Add the community validation paths
The validation framework now respect and look for community validation on the path. There config setting for enable_community_validations is respected. The community validations appear on validations list, can be called by group, category, id and so on seamless like any other validation. Change-Id: I4a6230ac5433028c7297fa88f77728c87d60173d Signed-off-by: Adriano Petrich (apetrich) <apetrich@redhat.com>
This commit is contained in:
parent
53b573230a
commit
5ba9c429d5
@ -126,8 +126,16 @@ class Ansible(object):
|
||||
gathering_policy, module_path, key,
|
||||
extra_env_variables, ansible_timeout,
|
||||
callback_whitelist, base_dir, python_interpreter,
|
||||
env={}):
|
||||
env={}, validation_cfg_file=None):
|
||||
"""Handle Ansible env var for Ansible config execution"""
|
||||
community_roles = ""
|
||||
community_library = ""
|
||||
community_lookup = ""
|
||||
if utils.community_validations_on(validation_cfg_file):
|
||||
community_roles = f"{constants.COMMUNITY_ROLES_DIR}:"
|
||||
community_library = f"{constants.COMMUNITY_LIBRARY_DIR}:"
|
||||
community_lookup = f"{constants.COMMUNITY_LOOKUP_DIR}:"
|
||||
|
||||
cwd = os.getcwd()
|
||||
env['ANSIBLE_SSH_ARGS'] = (
|
||||
'-o UserKnownHostsFile={} '
|
||||
@ -159,10 +167,12 @@ class Ansible(object):
|
||||
'{}:{}:'
|
||||
'/usr/share/ansible/plugins/modules:'
|
||||
'/usr/share/ceph-ansible/library:'
|
||||
'{community_path}'
|
||||
'{}/library'.format(
|
||||
os.path.join(workdir, 'modules'),
|
||||
os.path.join(cwd, 'modules'),
|
||||
base_dir
|
||||
base_dir,
|
||||
community_path=community_library
|
||||
)
|
||||
)
|
||||
env['ANSIBLE_LOOKUP_PLUGINS'] = os.path.expanduser(
|
||||
@ -170,10 +180,12 @@ class Ansible(object):
|
||||
'{}:{}:'
|
||||
'/usr/share/ansible/plugins/lookup:'
|
||||
'/usr/share/ceph-ansible/plugins/lookup:'
|
||||
'{community_path}'
|
||||
'{}/lookup_plugins'.format(
|
||||
os.path.join(workdir, 'lookup'),
|
||||
os.path.join(cwd, 'lookup'),
|
||||
base_dir
|
||||
base_dir,
|
||||
community_path=community_lookup
|
||||
)
|
||||
)
|
||||
env['ANSIBLE_CALLBACK_PLUGINS'] = os.path.expanduser(
|
||||
@ -215,10 +227,12 @@ class Ansible(object):
|
||||
'/usr/share/ansible/roles:'
|
||||
'/usr/share/ceph-ansible/roles:'
|
||||
'/etc/ansible/roles:'
|
||||
'{community_path}'
|
||||
'{}/roles'.format(
|
||||
os.path.join(workdir, 'roles'),
|
||||
os.path.join(cwd, 'roles'),
|
||||
base_dir
|
||||
base_dir,
|
||||
community_path=community_roles
|
||||
)
|
||||
)
|
||||
env['ANSIBLE_CALLBACK_WHITELIST'] = callback_whitelist
|
||||
@ -421,7 +435,8 @@ class Ansible(object):
|
||||
connection, gathering_policy,
|
||||
module_path, key, extra_env_variables,
|
||||
ansible_timeout, callback_whitelist,
|
||||
base_dir, python_interpreter))
|
||||
base_dir, python_interpreter,
|
||||
validation_cfg_file=validation_cfg_file))
|
||||
|
||||
if 'ANSIBLE_CONFIG' not in env and not ansible_cfg_file:
|
||||
ansible_cfg_file = os.path.join(ansible_artifact_path,
|
||||
|
@ -66,4 +66,5 @@ class ValidationList(BaseLister):
|
||||
v_actions = ValidationActions(validation_path=validation_dir)
|
||||
return (v_actions.list_validations(groups=group,
|
||||
categories=category,
|
||||
products=product))
|
||||
products=product,
|
||||
validation_config=self.base.config))
|
||||
|
@ -44,7 +44,8 @@ class Show(BaseShow):
|
||||
validation_name = parsed_args.validation_name
|
||||
|
||||
v_actions = ValidationActions(validation_path=validation_dir)
|
||||
data = v_actions.show_validations(validation_name)
|
||||
data = v_actions.show_validations(
|
||||
validation_name, validation_config=self.base.config)
|
||||
|
||||
if data:
|
||||
return data.keys(), data.values()
|
||||
@ -69,7 +70,9 @@ class ShowGroup(BaseLister):
|
||||
"""Take validation action"""
|
||||
|
||||
v_actions = ValidationActions(parsed_args.validation_dir)
|
||||
return v_actions.group_information(constants.VALIDATION_GROUPS_INFO)
|
||||
return v_actions.group_information(
|
||||
constants.VALIDATION_GROUPS_INFO,
|
||||
validation_config=self.base.config)
|
||||
|
||||
|
||||
class ShowParameter(BaseShow):
|
||||
@ -160,7 +163,8 @@ class ShowParameter(BaseShow):
|
||||
categories=parsed_args.category,
|
||||
products=parsed_args.product,
|
||||
output_format=parsed_args.format_output,
|
||||
download_file=parsed_args.download)
|
||||
download_file=parsed_args.download,
|
||||
validation_config=self.base.config)
|
||||
|
||||
if parsed_args.download:
|
||||
self.app.LOG.info(
|
||||
|
@ -248,7 +248,8 @@ FAKE_PLAYBOOK = [{'hosts': 'undercloud',
|
||||
'categories': ['os', 'storage'],
|
||||
'products': ['product1'],
|
||||
'name':
|
||||
'Advanced Format 512e Support'}}}]
|
||||
'Advanced Format 512e Support',
|
||||
'path': '/tmp'}}}]
|
||||
|
||||
FAKE_PLAYBOOK2 = [{'hosts': 'undercloud',
|
||||
'roles': ['advanced_format_512e_support'],
|
||||
@ -274,14 +275,16 @@ FAKE_METADATA = {'id': 'foo',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'categories': ['os', 'storage'],
|
||||
'products': ['product1'],
|
||||
'name': 'Advanced Format 512e Support'}
|
||||
'name': 'Advanced Format 512e Support',
|
||||
'path': '/tmp'}
|
||||
|
||||
FORMATED_DATA = {'Description': 'foo',
|
||||
'Groups': ['prep', 'pre-deployment'],
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': 'foo',
|
||||
'Name': 'Advanced Format 512e Support'}
|
||||
'Name': 'Advanced Format 512e Support',
|
||||
'Path': '/tmp'}
|
||||
|
||||
GROUP = {'no-op': [{'description': 'noop-foo'}],
|
||||
'pre': [{'description': 'pre-foo'}],
|
||||
|
@ -21,6 +21,7 @@ except ImportError:
|
||||
from unittest import TestCase
|
||||
|
||||
from ansible_runner import Runner
|
||||
from validations_libs import constants
|
||||
from validations_libs.ansible import Ansible
|
||||
from validations_libs.tests import fakes
|
||||
|
||||
@ -175,6 +176,56 @@ class TestAnsible(TestCase):
|
||||
'/tmp/foo/fact_cache'
|
||||
))
|
||||
|
||||
def test_ansible_env_var_with_community_validations(self):
|
||||
# AP No config file (use the default True)
|
||||
env = self.run._ansible_env_var(
|
||||
output_callback="", ssh_user="", workdir="", connection="",
|
||||
gathering_policy="", module_path="", key="",
|
||||
extra_env_variables="", ansible_timeout="",
|
||||
callback_whitelist="", base_dir="", python_interpreter="",
|
||||
env={}, validation_cfg_file=None)
|
||||
|
||||
assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" in env["ANSIBLE_LIBRARY"])
|
||||
assert(f"{constants.COMMUNITY_ROLES_DIR}:" in env["ANSIBLE_ROLES_PATH"])
|
||||
assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" in env["ANSIBLE_LOOKUP_PLUGINS"])
|
||||
|
||||
# AP config file with no settting (use the default True)
|
||||
env = self.run._ansible_env_var(
|
||||
output_callback="", ssh_user="", workdir="", connection="",
|
||||
gathering_policy="", module_path="", key="",
|
||||
extra_env_variables="", ansible_timeout="",
|
||||
callback_whitelist="", base_dir="", python_interpreter="",
|
||||
env={}, validation_cfg_file={"default": {}})
|
||||
|
||||
assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" in env["ANSIBLE_LIBRARY"])
|
||||
assert(f"{constants.COMMUNITY_ROLES_DIR}:" in env["ANSIBLE_ROLES_PATH"])
|
||||
assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" in env["ANSIBLE_LOOKUP_PLUGINS"])
|
||||
|
||||
# AP config file with settting True
|
||||
env = self.run._ansible_env_var(
|
||||
output_callback="", ssh_user="", workdir="", connection="",
|
||||
gathering_policy="", module_path="", key="",
|
||||
extra_env_variables="", ansible_timeout="",
|
||||
callback_whitelist="", base_dir="", python_interpreter="",
|
||||
env={}, validation_cfg_file={"default": {"enable_community_validations": True}})
|
||||
|
||||
assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" in env["ANSIBLE_LIBRARY"])
|
||||
assert(f"{constants.COMMUNITY_ROLES_DIR}:" in env["ANSIBLE_ROLES_PATH"])
|
||||
assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" in env["ANSIBLE_LOOKUP_PLUGINS"])
|
||||
|
||||
def test_ansible_env_var_without_community_validations(self):
|
||||
# AP config file with settting False
|
||||
env = self.run._ansible_env_var(
|
||||
output_callback="", ssh_user="", workdir="", connection="",
|
||||
gathering_policy="", module_path="", key="",
|
||||
extra_env_variables="", ansible_timeout="",
|
||||
callback_whitelist="", base_dir="", python_interpreter="",
|
||||
env={}, validation_cfg_file={"default": {"enable_community_validations": False}})
|
||||
|
||||
assert(f"{constants.COMMUNITY_LIBRARY_DIR}:" not in env["ANSIBLE_LIBRARY"])
|
||||
assert(f"{constants.COMMUNITY_ROLES_DIR}:" not in env["ANSIBLE_ROLES_PATH"])
|
||||
assert(f"{constants.COMMUNITY_LOOKUP_DIR}:" not in env["ANSIBLE_LOOKUP_PLUGINS"])
|
||||
|
||||
def test_get_extra_vars_dict(self):
|
||||
extra_vars = {
|
||||
'foo': 'bar'
|
||||
|
@ -44,10 +44,48 @@ class TestUtils(TestCase):
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': '512e',
|
||||
'Parameters': {}}
|
||||
'Parameters': {},
|
||||
'Path': '/tmp'}
|
||||
res = utils.get_validations_data('512e')
|
||||
self.assertEqual(res, output)
|
||||
|
||||
@mock.patch('validations_libs.validation.Validation._get_content',
|
||||
return_value=fakes.FAKE_PLAYBOOK[0])
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch('os.path.exists', side_effect=(False, True))
|
||||
def test_get_community_validations_data(self, mock_exists, mock_open, mock_data):
|
||||
"""
|
||||
The main difference between this test and test_get_validations_data
|
||||
is that this one tries to load first the validations_commons validation
|
||||
then it fails as os.path.exists returns false and then looks for it in the
|
||||
community validations.
|
||||
"""
|
||||
output = {'Name': 'Advanced Format 512e Support',
|
||||
'Description': 'foo', 'Groups': ['prep', 'pre-deployment'],
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': '512e',
|
||||
'Parameters': {},
|
||||
'Path': '/tmp'}
|
||||
res = utils.get_validations_data('512e')
|
||||
self.assertEqual(res, output)
|
||||
|
||||
@mock.patch('validations_libs.validation.Validation._get_content',
|
||||
return_value=fakes.FAKE_PLAYBOOK[0])
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch('os.path.exists', side_effect=(False, True))
|
||||
def test_get_community_disabled_validations_data(self, mock_exists, mock_open, mock_data):
|
||||
"""
|
||||
This test is similar to test_get_community_validations_data in the sense that it
|
||||
doesn't find the validations_commons one and should look for community validations
|
||||
but the setting is disabled by the config so it shouldn't find any validations
|
||||
"""
|
||||
output = {}
|
||||
res = utils.get_validations_data(
|
||||
'512e',
|
||||
validation_config={'default': {"enable_community_validations": False}})
|
||||
self.assertEqual(res, output)
|
||||
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
def test_get_validations_data_wrong_type(self, mock_exists):
|
||||
validation = ['val1']
|
||||
@ -60,11 +98,33 @@ class TestUtils(TestCase):
|
||||
@mock.patch('glob.glob')
|
||||
def test_parse_all_validations_on_disk(self, mock_glob, mock_open,
|
||||
mock_load):
|
||||
mock_glob.return_value = \
|
||||
['/foo/playbook/foo.yaml']
|
||||
mock_glob.side_effect = \
|
||||
(['/foo/playbook/foo.yaml'], [])
|
||||
result = utils.parse_all_validations_on_disk('/foo/playbook')
|
||||
self.assertEqual(result, [fakes.FAKE_METADATA])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch('glob.glob')
|
||||
def test_parse_community_validations_on_disk(
|
||||
self, mock_glob, mock_open, mock_load):
|
||||
mock_glob.side_effect = \
|
||||
([], ['/foo/playbook/foo.yaml'])
|
||||
result = utils.parse_all_validations_on_disk('/foo/playbook')
|
||||
self.assertEqual(result, [fakes.FAKE_METADATA])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
@mock.patch('glob.glob')
|
||||
def test_parse_all_community_disabled_validations_on_disk(
|
||||
self, mock_glob, mock_open, mock_load):
|
||||
mock_glob.side_effect = \
|
||||
([], ['/foo/playbook/foo.yaml'])
|
||||
result = utils.parse_all_validations_on_disk(
|
||||
'/foo/playbook',
|
||||
validation_config={'default': {"enable_community_validations": False}})
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_parse_all_validations_on_disk_wrong_path_type(self):
|
||||
self.assertRaises(TypeError,
|
||||
utils.parse_all_validations_on_disk,
|
||||
@ -118,8 +178,8 @@ class TestUtils(TestCase):
|
||||
def test_parse_all_validations_on_disk_by_group(self, mock_glob,
|
||||
mock_open,
|
||||
mock_load):
|
||||
mock_glob.return_value = \
|
||||
['/foo/playbook/foo.yaml']
|
||||
mock_glob.side_effect = \
|
||||
(['/foo/playbook/foo.yaml'], [])
|
||||
result = utils.parse_all_validations_on_disk('/foo/playbook',
|
||||
['prep'])
|
||||
self.assertEqual(result, [fakes.FAKE_METADATA])
|
||||
@ -130,8 +190,8 @@ class TestUtils(TestCase):
|
||||
def test_parse_all_validations_on_disk_by_category(self, mock_glob,
|
||||
mock_open,
|
||||
mock_load):
|
||||
mock_glob.return_value = \
|
||||
['/foo/playbook/foo.yaml']
|
||||
mock_glob.side_effect = \
|
||||
(['/foo/playbook/foo.yaml'], [])
|
||||
result = utils.parse_all_validations_on_disk('/foo/playbook',
|
||||
categories=['os'])
|
||||
self.assertEqual(result, [fakes.FAKE_METADATA])
|
||||
@ -147,31 +207,80 @@ class TestUtils(TestCase):
|
||||
def test_parse_all_validations_on_disk_by_product(self, mock_glob,
|
||||
mock_open,
|
||||
mock_load):
|
||||
mock_glob.return_value = \
|
||||
['/foo/playbook/foo.yaml']
|
||||
mock_glob.side_effect = (['/foo/playbook/foo.yaml'], [])
|
||||
result = utils.parse_all_validations_on_disk('/foo/playbook',
|
||||
products=['product1'])
|
||||
self.assertEqual(result, [fakes.FAKE_METADATA])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('os.listdir')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_validations_playbook_by_id(self, mock_open, mock_load,
|
||||
mock_listdir, mock_isfile):
|
||||
mock_listdir.return_value = ['foo.yaml']
|
||||
mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (['/foo/playbook/foo.yaml'], [])
|
||||
mock_isfile.return_value = True
|
||||
result = utils.get_validations_playbook('/foo/playbook',
|
||||
validation_id=['foo'])
|
||||
self.assertEqual(result, ['/foo/playbook/foo.yaml'])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('os.listdir')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_community_playbook_by_id(self, mock_open, mock_load,
|
||||
mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (
|
||||
[],
|
||||
['/home/foo/community-validations/playbooks/foo.yaml'])
|
||||
mock_isfile.return_value = True
|
||||
# AP this needs a bit of an explanation. We look at the explicity at
|
||||
# the /foo/playbook directory but the community validation path is
|
||||
# implicit and we find there the id that we are looking for.
|
||||
result = utils.get_validations_playbook('/foo/playbook',
|
||||
validation_id=['foo'])
|
||||
self.assertEqual(result, ['/home/foo/community-validations/playbooks/foo.yaml'])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_community_disabled_playbook_by_id(
|
||||
self, mock_open, mock_load, mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (
|
||||
[],
|
||||
['/home/foo/community-validations/playbooks/foo.yaml'])
|
||||
mock_isfile.return_value = True
|
||||
# The validations_commons validation is not found and community_vals is disabled
|
||||
# So no validation should be found.
|
||||
result = utils.get_validations_playbook(
|
||||
'/foo/playbook',
|
||||
validation_id=['foo'],
|
||||
validation_config={'default': {"enable_community_validations": False}})
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_community_playbook_by_id_not_found(
|
||||
self, mock_open, mock_load, mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (
|
||||
[],
|
||||
['/home/foo/community-validations/playbooks/foo.yaml/'])
|
||||
# the is file fails
|
||||
mock_isfile.return_value = False
|
||||
result = utils.get_validations_playbook('/foo/playbook',
|
||||
validation_id=['foo'])
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_validations_playbook_by_id_group(self, mock_open, mock_load,
|
||||
mock_listdir, mock_isfile):
|
||||
mock_listdir.return_value = ['foo.yaml']
|
||||
mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (['/foo/playbook/foo.yaml'], [])
|
||||
mock_isfile.return_value = True
|
||||
result = utils.get_validations_playbook('/foo/playbook', ['foo'], ['prep'])
|
||||
self.assertEqual(result, ['/foo/playbook/foo.yaml',
|
||||
@ -192,24 +301,24 @@ class TestUtils(TestCase):
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('os.listdir')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_validations_playbook_by_category(self, mock_open, mock_load,
|
||||
mock_listdir, mock_isfile):
|
||||
mock_listdir.return_value = ['foo.yaml']
|
||||
mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (['/foo/playbook/foo.yaml'], [])
|
||||
mock_isfile.return_value = True
|
||||
result = utils.get_validations_playbook('/foo/playbook',
|
||||
categories=['os', 'storage'])
|
||||
self.assertEqual(result, ['/foo/playbook/foo.yaml'])
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('os.listdir')
|
||||
@mock.patch('glob.glob')
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_validations_playbook_by_product(self, mock_open, mock_load,
|
||||
mock_listdir, mock_isfile):
|
||||
mock_listdir.return_value = ['foo.yaml']
|
||||
mock_glob, mock_isfile):
|
||||
mock_glob.side_effect = (['/foo/playbook/foo.yaml'], [])
|
||||
mock_isfile.return_value = True
|
||||
result = utils.get_validations_playbook('/foo/playbook',
|
||||
products=['product1'])
|
||||
|
@ -15,8 +15,10 @@
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
from unittest.mock import ANY
|
||||
except ImportError:
|
||||
import mock
|
||||
from mock import ANY
|
||||
|
||||
import json
|
||||
|
||||
@ -209,7 +211,8 @@ class TestValidationActions(TestCase):
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'id': 'foo',
|
||||
'name': 'My Validition One Name',
|
||||
'parameters': {}}]
|
||||
'parameters': {},
|
||||
'path': '/tmp/foobar/validation-playbooks'}]
|
||||
|
||||
mock_ansible_run.return_value = ('foo.yaml', 0, 'successful')
|
||||
|
||||
@ -224,6 +227,29 @@ class TestValidationActions(TestCase):
|
||||
validations_dir='/tmp/foo')
|
||||
self.assertEqual(run_return, expected_run_return)
|
||||
|
||||
mock_ansible_run.assert_called_with(
|
||||
workdir=ANY,
|
||||
playbook='/tmp/foobar/validation-playbooks/foo.yaml',
|
||||
base_dir='/usr/share/ansible',
|
||||
playbook_dir='/tmp/foobar/validation-playbooks',
|
||||
parallel_run=True,
|
||||
inventory='tmp/inventory.yaml',
|
||||
output_callback='validation_stdout',
|
||||
callback_whitelist=None,
|
||||
quiet=True,
|
||||
extra_vars=None,
|
||||
limit_hosts=None,
|
||||
extra_env_variables=None,
|
||||
ansible_cfg_file=None,
|
||||
gathering_policy='explicit',
|
||||
ansible_artifact_path=ANY,
|
||||
log_path=ANY,
|
||||
run_async=False,
|
||||
python_interpreter=None,
|
||||
ssh_user=None,
|
||||
validation_cfg_file=None
|
||||
)
|
||||
|
||||
@mock.patch('validations_libs.utils.get_validations_playbook')
|
||||
def test_validation_run_wrong_validation_name(self, mock_validation_play):
|
||||
mock_validation_play.return_value = []
|
||||
@ -234,6 +260,31 @@ class TestValidationActions(TestCase):
|
||||
validations_dir='/tmp/foo'
|
||||
)
|
||||
|
||||
@mock.patch('validations_libs.utils.get_validations_playbook')
|
||||
def test_validation_run_not_all_found(self, mock_validation_play):
|
||||
|
||||
mock_validation_play.return_value = ['/tmp/foo/fake.yaml']
|
||||
run = ValidationActions()
|
||||
try:
|
||||
run.run_validations(
|
||||
validation_name=['fake', 'foo'],
|
||||
validations_dir='/tmp/foo')
|
||||
except RuntimeError as runtime_error:
|
||||
self.assertEqual(
|
||||
"Validation ['foo'] not found in /tmp/foo.",
|
||||
str(runtime_error))
|
||||
else:
|
||||
self.fail("Runtime error exception should have been raised")
|
||||
|
||||
@mock.patch('validations_libs.utils.parse_all_validations_on_disk')
|
||||
def test_validation_run_not_enough_params(self, mock_validation_play):
|
||||
mock_validation_play.return_value = []
|
||||
|
||||
run = ValidationActions()
|
||||
self.assertRaises(RuntimeError, run.run_validations,
|
||||
validations_dir='/tmp/foo'
|
||||
)
|
||||
|
||||
@mock.patch('validations_libs.utils.os.makedirs')
|
||||
@mock.patch('validations_libs.utils.os.access', return_value=True)
|
||||
@mock.patch('validations_libs.utils.os.path.exists', return_value=True)
|
||||
@ -250,7 +301,8 @@ class TestValidationActions(TestCase):
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'id': 'foo',
|
||||
'name': 'My Validition One Name',
|
||||
'parameters': {}}]
|
||||
'parameters': {},
|
||||
'path': '/usr/share/ansible/validation-playbooks'}]
|
||||
|
||||
mock_ansible_run.return_value = ('foo.yaml', 0, 'failed')
|
||||
|
||||
@ -295,7 +347,8 @@ class TestValidationActions(TestCase):
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'id': 'foo',
|
||||
'name': 'My Validition One Name',
|
||||
'parameters': {}}]
|
||||
'parameters': {},
|
||||
'path': '/usr/share/ansible/validation-playbooks'}]
|
||||
playbook = ['fake.yaml']
|
||||
inventory = 'tmp/inventory.yaml'
|
||||
|
||||
@ -321,7 +374,8 @@ class TestValidationActions(TestCase):
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'id': 'foo',
|
||||
'name': 'My Validition One Name',
|
||||
'parameters': {}}]
|
||||
'parameters': {},
|
||||
'path': '/usr/share/ansible/validation-playbooks'}]
|
||||
playbook = ['fake.yaml']
|
||||
inventory = 'tmp/inventory.yaml'
|
||||
|
||||
@ -357,7 +411,9 @@ class TestValidationActions(TestCase):
|
||||
'Categories': ['os', 'storage'],
|
||||
'Products': ['product1'],
|
||||
'ID': '512e',
|
||||
'Parameters': {}}
|
||||
'Parameters': {},
|
||||
'Path': '/tmp'
|
||||
}
|
||||
data.update({'Last execution date': '2019-11-25 13:40:14',
|
||||
'Number of execution': 'Total: 1, Passed: 0, Failed: 1'})
|
||||
validations_show = ValidationActions()
|
||||
|
@ -37,6 +37,21 @@ def current_time():
|
||||
return '%sZ' % datetime.datetime.utcnow().isoformat()
|
||||
|
||||
|
||||
def community_validations_on(validation_config):
|
||||
"""Check for flag for community validations to be enabled
|
||||
The default value is true
|
||||
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
:return: A boolean with the status of community validations flag
|
||||
:rtype: `bool`
|
||||
"""
|
||||
if not validation_config:
|
||||
return True
|
||||
return validation_config.get("default", {}).get("enable_community_validations", True)
|
||||
|
||||
|
||||
def create_log_dir(log_path=constants.VALIDATIONS_LOG_BASEDIR):
|
||||
"""Check for presence of the selected validations log dir.
|
||||
Create the directory if needed, and use fallback if that
|
||||
@ -136,7 +151,8 @@ def create_artifacts_dir(log_path=constants.VALIDATIONS_LOG_BASEDIR,
|
||||
def parse_all_validations_on_disk(path,
|
||||
groups=None,
|
||||
categories=None,
|
||||
products=None):
|
||||
products=None,
|
||||
validation_config=None):
|
||||
"""Return a list of validations metadata which can be sorted by Groups, by
|
||||
Categories or by Products.
|
||||
|
||||
@ -152,6 +168,10 @@ def parse_all_validations_on_disk(path,
|
||||
:param products: Products of validations
|
||||
:type products: `list`
|
||||
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
|
||||
:return: A list of validations metadata.
|
||||
:rtype: `list`
|
||||
|
||||
@ -192,6 +212,9 @@ def parse_all_validations_on_disk(path,
|
||||
|
||||
results = []
|
||||
validations_abspath = glob.glob("{path}/*.yaml".format(path=path))
|
||||
if community_validations_on(validation_config):
|
||||
validations_abspath.extend(glob.glob("{}/*.yaml".format(
|
||||
constants.COMMUNITY_PLAYBOOKS_DIR)))
|
||||
|
||||
LOG.debug(
|
||||
"Attempting to parse validations by:\n"
|
||||
@ -200,7 +223,6 @@ def parse_all_validations_on_disk(path,
|
||||
" - products: {}\n"
|
||||
"from {}".format(groups, categories, products, validations_abspath)
|
||||
)
|
||||
|
||||
for playbook in validations_abspath:
|
||||
val = Validation(playbook)
|
||||
|
||||
@ -220,7 +242,8 @@ def get_validations_playbook(path,
|
||||
validation_id=None,
|
||||
groups=None,
|
||||
categories=None,
|
||||
products=None):
|
||||
products=None,
|
||||
validation_config=None):
|
||||
"""Get a list of validations playbooks paths either by their names,
|
||||
their groups, by their categories or by their products.
|
||||
|
||||
@ -239,6 +262,10 @@ def get_validations_playbook(path,
|
||||
:param products: List of validation product
|
||||
:type products: `list`
|
||||
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
|
||||
:return: A list of absolute validations playbooks path
|
||||
:rtype: `list`
|
||||
|
||||
@ -281,12 +308,15 @@ def get_validations_playbook(path,
|
||||
raise TypeError("The 'products' argument must be a List")
|
||||
|
||||
pl = []
|
||||
for f in os.listdir(path):
|
||||
pl_path = join(path, f)
|
||||
validations_abspath = glob.glob("{path}/*.yaml".format(path=path))
|
||||
if community_validations_on(validation_config):
|
||||
validations_abspath.extend(glob.glob("{}/*.yaml".format(
|
||||
constants.COMMUNITY_PLAYBOOKS_DIR)))
|
||||
for pl_path in validations_abspath:
|
||||
if os.path.isfile(pl_path):
|
||||
if validation_id:
|
||||
if os.path.splitext(f)[0] in validation_id or \
|
||||
os.path.basename(f) in validation_id:
|
||||
if os.path.splitext(os.path.basename(pl_path))[0] in validation_id or \
|
||||
os.path.basename(pl_path) in validation_id:
|
||||
pl.append(pl_path)
|
||||
|
||||
val = Validation(pl_path)
|
||||
@ -377,7 +407,10 @@ def get_validations_details(validation):
|
||||
return {}
|
||||
|
||||
|
||||
def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR):
|
||||
def get_validations_data(
|
||||
validation,
|
||||
path=constants.ANSIBLE_VALIDATION_DIR,
|
||||
validation_config=None):
|
||||
"""Return validation data with format:
|
||||
|
||||
ID, Name, Description, Groups, Parameters
|
||||
@ -387,6 +420,9 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR):
|
||||
:type validation: `string`
|
||||
:param path: The path to the validations directory
|
||||
:type path: `string`
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
:return: The validation data with the format
|
||||
(ID, Name, Description, Groups, Parameters)
|
||||
:rtype: `dict`
|
||||
@ -408,6 +444,9 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR):
|
||||
|
||||
data = {}
|
||||
val_path = "{}/{}.yaml".format(path, validation)
|
||||
comm_path = ""
|
||||
if community_validations_on(validation_config):
|
||||
comm_path = "{}/{}.yaml".format(constants.COMMUNITY_PLAYBOOKS_DIR, validation)
|
||||
|
||||
LOG.debug(
|
||||
"Obtaining information about validation {} from {}".format(
|
||||
@ -419,6 +458,11 @@ def get_validations_data(validation, path=constants.ANSIBLE_VALIDATION_DIR):
|
||||
val = Validation(val_path)
|
||||
data.update(val.get_formated_data)
|
||||
data.update({'Parameters': val.get_vars})
|
||||
if not data and comm_path:
|
||||
if os.path.exists(comm_path):
|
||||
val = Validation(comm_path)
|
||||
data.update(val.get_formated_data)
|
||||
data.update({'Parameters': val.get_vars})
|
||||
return data
|
||||
|
||||
|
||||
|
@ -89,6 +89,7 @@ class Validation(object):
|
||||
def __init__(self, validation_path):
|
||||
self.dict = self._get_content(validation_path)
|
||||
self.id = os.path.splitext(os.path.basename(validation_path))[0]
|
||||
self.path = os.path.dirname(validation_path)
|
||||
|
||||
def _get_content(self, val_path):
|
||||
try:
|
||||
@ -176,10 +177,11 @@ class Validation(object):
|
||||
'categories': ['category1', 'category2'],
|
||||
'products': ['product1', 'product2'],
|
||||
'id': 'val1',
|
||||
'name': 'The validation val1\'s name'}
|
||||
'name': 'The validation val1\'s name',
|
||||
'path': '/tmp/foo/'}
|
||||
"""
|
||||
if self.has_metadata_dict:
|
||||
self.metadata = {'id': self.id}
|
||||
self.metadata = {'id': self.id, 'path': self.path}
|
||||
self.metadata.update(self.dict['vars'].get('metadata'))
|
||||
return self.metadata
|
||||
else:
|
||||
@ -353,7 +355,8 @@ class Validation(object):
|
||||
'Description': 'description of val',
|
||||
'Groups': ['group1', 'group2'],
|
||||
'ID': 'val',
|
||||
'Name': 'validation one'}
|
||||
'Name': 'validation one',
|
||||
'path': '/tmp/foo/'}
|
||||
"""
|
||||
data = {}
|
||||
metadata = self.get_metadata
|
||||
|
@ -50,7 +50,8 @@ class ValidationActions(object):
|
||||
def list_validations(self,
|
||||
groups=None,
|
||||
categories=None,
|
||||
products=None):
|
||||
products=None,
|
||||
validation_config=None):
|
||||
"""Get a list of the validations selected by group membership or by
|
||||
category. With their names, group membership information, categories and
|
||||
products.
|
||||
@ -66,6 +67,10 @@ class ValidationActions(object):
|
||||
:param products: List of validation products.
|
||||
:type products: `list`
|
||||
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
|
||||
:return: Column names and a list of the selected validations
|
||||
:rtype: `tuple`
|
||||
|
||||
@ -106,7 +111,8 @@ class ValidationActions(object):
|
||||
path=self.validation_path,
|
||||
groups=groups,
|
||||
categories=categories,
|
||||
products=products
|
||||
products=products,
|
||||
validation_config=validation_config
|
||||
)
|
||||
|
||||
self.log.debug(
|
||||
@ -124,13 +130,18 @@ class ValidationActions(object):
|
||||
return (column_names, return_values)
|
||||
|
||||
def show_validations(self, validation,
|
||||
log_path=constants.VALIDATIONS_LOG_BASEDIR):
|
||||
log_path=constants.VALIDATIONS_LOG_BASEDIR,
|
||||
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
|
||||
:type log_path: `string`
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
|
||||
|
||||
:return: The detailed information for a validation
|
||||
:rtype: `dict`
|
||||
@ -156,11 +167,18 @@ class ValidationActions(object):
|
||||
self.log = logging.getLogger(__name__ + ".show_validations")
|
||||
# Get validation data:
|
||||
vlog = ValidationLogs(log_path)
|
||||
data = v_utils.get_validations_data(validation, self.validation_path)
|
||||
if not data:
|
||||
msg = "Validation {} not found in the path: {}".format(
|
||||
data = v_utils.get_validations_data(
|
||||
validation,
|
||||
self.validation_path)
|
||||
self.validation_path,
|
||||
validation_config=validation_config)
|
||||
if not data:
|
||||
extra_msg = ""
|
||||
if v_utils.community_validations_on(validation_config):
|
||||
extra_msg = " or {}".format(constants.COMMUNITY_LIBRARY_DIR)
|
||||
msg = "Validation {} not found in the path: {}{}".format(
|
||||
validation,
|
||||
self.validation_path,
|
||||
extra_msg)
|
||||
raise RuntimeError(msg)
|
||||
logfiles = vlog.get_logfile_content_by_validation(validation)
|
||||
data_format = vlog.get_validations_stats(logfiles)
|
||||
@ -331,15 +349,15 @@ class ValidationActions(object):
|
||||
(Defaults to 'None')
|
||||
:type skip_list: ``dict``
|
||||
|
||||
:return: A list of dictionary containing the informations of the
|
||||
validations executions (Validations, Duration, Host_Group,
|
||||
Status, Status_by_Host, UUID and Unreachable_Hosts)
|
||||
:rtype: ``list``
|
||||
:param ssh_user: Ssh user for Ansible remote connection
|
||||
:type ssh_user: ``string``
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
:return: A list of dictionary containing the informations of the
|
||||
validations executions (Validations, Duration, Host_Group,
|
||||
Status, Status_by_Host, UUID and Unreachable_Hosts)
|
||||
:rtype: ``list``
|
||||
|
||||
:Example:
|
||||
|
||||
@ -378,13 +396,16 @@ class ValidationActions(object):
|
||||
)
|
||||
validations = v_utils.parse_all_validations_on_disk(
|
||||
path=validations_dir, groups=group,
|
||||
categories=category, products=product
|
||||
categories=category, products=product,
|
||||
validation_config=validation_config
|
||||
)
|
||||
for val in validations:
|
||||
playbooks.append(val.get('id') + '.yaml')
|
||||
playbooks.append("{path}/{id}.yaml".format(**val))
|
||||
elif validation_name:
|
||||
playbooks = v_utils.get_validations_playbook(validations_dir,
|
||||
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 = []
|
||||
@ -419,7 +440,7 @@ class ValidationActions(object):
|
||||
workdir=artifacts_dir,
|
||||
playbook=playbook,
|
||||
base_dir=base_dir,
|
||||
playbook_dir=validations_dir,
|
||||
playbook_dir=os.path.dirname(playbook),
|
||||
parallel_run=True,
|
||||
inventory=inventory,
|
||||
output_callback=output_callback,
|
||||
@ -441,7 +462,7 @@ class ValidationActions(object):
|
||||
workdir=artifacts_dir,
|
||||
playbook=playbook,
|
||||
base_dir=base_dir,
|
||||
playbook_dir=validations_dir,
|
||||
playbook_dir=os.path.dirname(playbook),
|
||||
parallel_run=True,
|
||||
inventory=inventory,
|
||||
output_callback=output_callback,
|
||||
@ -483,7 +504,7 @@ class ValidationActions(object):
|
||||
vlog = ValidationLogs(log_path)
|
||||
return vlog.get_results(uuid)
|
||||
|
||||
def group_information(self, groups):
|
||||
def group_information(self, groups, validation_config=None):
|
||||
"""Get Information about Validation Groups
|
||||
|
||||
This is used to print table from python ``Tuple`` with ``PrettyTable``.
|
||||
@ -500,6 +521,9 @@ class ValidationActions(object):
|
||||
|
||||
:param groups: The absolute path of the groups.yaml file
|
||||
:type groups: ``string``
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
|
||||
:return: The list of the available groups with their description and
|
||||
the numbers of validation belonging to them.
|
||||
@ -523,7 +547,8 @@ class ValidationActions(object):
|
||||
|
||||
validations = v_utils.parse_all_validations_on_disk(
|
||||
path=self.validation_path,
|
||||
groups=[group[0] for group in group_definitions])
|
||||
groups=[group[0] for group in group_definitions],
|
||||
validation_config=validation_config)
|
||||
|
||||
# Get validations number by group
|
||||
for group in group_definitions:
|
||||
@ -543,7 +568,8 @@ class ValidationActions(object):
|
||||
categories=None,
|
||||
products=None,
|
||||
output_format='json',
|
||||
download_file=None):
|
||||
download_file=None,
|
||||
validation_config=None):
|
||||
"""
|
||||
Return Validations Parameters for one or several validations by their
|
||||
names, their groups, by their categories or by their products.
|
||||
@ -566,6 +592,9 @@ class ValidationActions(object):
|
||||
:param download_file: Path of a file in which the parameters will be
|
||||
stored
|
||||
:type download_file: `string`
|
||||
:param validation_config: A dictionary of configuration for Validation
|
||||
loaded from an validation.cfg file.
|
||||
:type validation_config: ``dict``
|
||||
|
||||
:return: A JSON or a YAML dump (By default, JSON).
|
||||
if `download_file` is used, a file containing only the
|
||||
@ -622,7 +651,8 @@ class ValidationActions(object):
|
||||
validation_id=validations,
|
||||
groups=groups,
|
||||
categories=categories,
|
||||
products=products
|
||||
products=products,
|
||||
validation_config=validation_config
|
||||
)
|
||||
|
||||
params = v_utils.get_validations_parameters(
|
||||
|
Loading…
x
Reference in New Issue
Block a user