Add validation config file mechanism

Change-Id: I05c54a43bc0a03878793cca3f51e23f4a8b63a23
This commit is contained in:
matbu 2021-05-20 19:09:37 +02:00 committed by mbu
parent 26bea93d28
commit e5e89a6640
17 changed files with 468 additions and 67 deletions

View File

@ -22,6 +22,10 @@ classifier =
[files]
packages = validations_libs
[options.data_files]
/etc =
validation.cfg
[compile_catalog]
directory = validations-libs/locale
domain = validations-lib

89
validation.cfg Normal file
View File

@ -0,0 +1,89 @@
[default]
# Default configuration for the Validation Framework
# These are mainly CLI parameters which can be set here in order to avoid
# to provide the same parameters on each runs.
# Location where the Validation playbooks are stored.
validation_dir = /usr/share/ansible/validation-playbooks
# Log path where the framework is supposed to write logs and results.
validation_log_dir = validations
# Location where the Ansible Validation Callback, Libraries and Modules are
# stored.
ansible_base_dir = /usr/share/ansible/
# Ssh user for the remote access
ssh_user = stack
# Output log for the Validation results.
output_log = output.log
# Limitation of the number of results to return to the console.
history_limit = 15
[ansible_runner]
# Ansible Runner configuration parameters.
# Here you can set the Runner parameters which will be used by the framework.
# Note that only those parameters are supported, any other custom parameters
# will be ignored.
# Verbosity for Ansible
verbosity = 5
# Example for check-ram Validation
extravars = {'minimal_ram_gb': 2}
# Fact cache directory location and type
# fact_cache = /var/log/validations/artifacts/
fact_cache_type = jsonfile
# Inventory for Ansible
# inventory = hosts.yaml
# Playbook path
# playbook = /usr/share/ansible/validation-playbooks/check-ram.yaml
project_dir = /usr/share/ansible/validation-playbooks
quiet = True
rotate_artifacts = 256
[ansible_environment]
# Ansible Environment variables.
# You can provide here, all the Ansible configuration variables documented here:
# https://docs.ansible.com/ansible/latest/reference_appendices/config.html
# Here is a set of parameters used by the Validation Framework as example:
ANSIBLE_ACTION_PLUGINS = /home/stack/.ansible/plugins/action:/home/stack/action:/usr/share/ansible/plugins/action:/usr/share/ceph-ansible/plugins/actions:/usr/share/ansible//action_plugins
ANSIBLE_CACHE_PLUGIN_TIMEOUT = 7200
ANSIBLE_CALLBACK_PLUGINS = /home/stack/.ansible/plugins/callback:/home/stack/callback:/usr/share/ansible/plugins/callback:/usr/share/ceph-ansible/plugins/callback:/usr/share/ansible//callback_plugins
ANSIBLE_CALLBACK_WHITELIST = validation_stdout,validation_json,profile_tasks
#ANSIBLE_CONFIG = ansible.cfg
ANSIBLE_DISPLAY_FAILED_STDERR = True
ANSIBLE_FILTER_PLUGINS = /home/stack/.ansible/plugins/filter:/home/stack/filter:/usr/share/ansible/plugins/filter:/usr/share/ceph-ansible/plugins/filter:/usr/share/ansible//filter_plugins
ANSIBLE_FORKS = 36
ANSIBLE_GATHERING = explicit
ANSIBLE_GATHER_TIMEOUT = 45
ANSIBLE_HOST_KEY_CHECKING = False
ANSIBLE_LIBRARY = /home/stack/.ansible/plugins/modules:/home/stack/modules:/usr/share/ansible/plugins/modules:/usr/share/ceph-ansible/library:/usr/share/ansible//library
ANSIBLE_LOG_PATH = /home/stack/ansible.log
ANSIBLE_LOOKUP_PLUGINS = /home/stack/.ansible/plugins/lookup::/home/stack/lookup:/usr/share/ansible/plugins/lookup:/usr/share/ceph-ansible/plugins/lookup:/usr/share/ansible//lookup_plugins
ANSIBLE_PIPELINING = True
ANSIBLE_REMOTE_USER = stack
ANSIBLE_RETRY_FILES_ENABLED = False
ANSIBLE_ROLES_PATH = /home/stack/.ansible/roles:/home/stack/roles:/usr/share/ansible/roles:/usr/share/ceph-ansible/roles:/etc/ansible/roles:/usr/share/ansible//roles
ANSIBLE_SSH_ARGS = -o UserKnownHostsFile=/dev/null -o
StrictHostKeyChecking=no -o ControlMaster=auto -o
ControlPersist=30m -o ServerAliveInterval=64 -o
ServerAliveCountMax=1024 -o Compression=no -o
TCPKeepAlive=yes -o VerifyHostKeyDNS=no -o ForwardX11=no
-o ForwardAgent=yes -o
PreferredAuthentications=publickey -T
ANSIBLE_SSH_RETRIES = 3
ANSIBLE_STDOUT_CALLBACK = validation_stdout
ANSIBLE_TIMEOUT = 30
ANSIBLE_TRANSPORT = smart
# Callback settings which are part of Ansible environment variables.
# Configuration for HTTP Server callback
HTTP_JSON_SERVER = http://localhost
HTTP_JSON_PORT = 8080

View File

@ -26,6 +26,7 @@ import yaml
from six.moves import configparser
from validations_libs import constants
from validations_libs import utils
LOG = logging.getLogger(__name__ + ".ansible")
@ -275,6 +276,17 @@ class Ansible(object):
else:
return env
def _dump_validation_config(self, config, path, filename='validation.cfg'):
"""Dump Validation config in artifact directory"""
parser = configparser.ConfigParser()
for section_key in config.keys():
parser.add_section(section_key)
for item_key in config[section_key].keys():
parser.set(section_key, item_key,
str(config[section_key][item_key]))
with open('{}/{}'.format(path, filename), 'w') as conf:
parser.write(conf)
def run(self, playbook, inventory, workdir, playbook_dir=None,
connection='smart', output_callback=None,
base_dir=constants.DEFAULT_VALIDATIONS_BASEDIR,
@ -283,9 +295,10 @@ class Ansible(object):
verbosity=0, quiet=False, extra_vars=None,
gathering_policy='smart',
extra_env_variables=None, parallel_run=False,
callback_whitelist=None, ansible_cfg=None,
callback_whitelist=None, ansible_cfg_file=None,
ansible_timeout=30, ansible_artifact_path=None,
log_path=None, run_async=False, python_interpreter=None):
log_path=None, run_async=False, python_interpreter=None,
config=None):
"""Execute one or multiple Ansible playbooks
:param playbook: The Absolute path of the Ansible playbook
@ -341,9 +354,10 @@ class Ansible(object):
Custom output_callback is also whitelisted.
(Defaults to ``None``)
:type callback_whitelist: ``list`` or ``string``
:param ansible_cfg: Path to an ansible configuration file. One will be
generated in the artifact path if this option is None.
:type ansible_cfg: ``string``
:param ansible_cfg_file: Path to an ansible configuration file. One
will be generated in the artifact path if
this option is None.
:type ansible_cfg_file: ``string``
:param ansible_timeout: Timeout for ansible connections.
(Defaults to ``30 minutes``)
:type ansible_timeout: ``integer``
@ -360,6 +374,9 @@ class Ansible(object):
``auto_silent`` or the default one
``auto_legacy``)
:type python_interpreter: ``string``
:param config: A dictionary of configuration for Validation loaded
from an validation.cfg file.
:type config: ``dict``
:return: A ``tuple`` containing the the absolute path of the executed
playbook, the return code and the status of the run
@ -405,16 +422,17 @@ class Ansible(object):
ansible_timeout, callback_whitelist,
base_dir, python_interpreter))
if 'ANSIBLE_CONFIG' not in env and not ansible_cfg:
ansible_cfg = os.path.join(ansible_artifact_path, 'ansible.cfg')
config = configparser.ConfigParser()
config.add_section('defaults')
config.set('defaults', 'internal_poll_interval', '0.05')
with open(ansible_cfg, 'w') as f:
config.write(f)
env['ANSIBLE_CONFIG'] = ansible_cfg
elif 'ANSIBLE_CONFIG' not in env and ansible_cfg:
env['ANSIBLE_CONFIG'] = ansible_cfg
if 'ANSIBLE_CONFIG' not in env and not ansible_cfg_file:
ansible_cfg_file = os.path.join(ansible_artifact_path,
'ansible.cfg')
ansible_config = configparser.ConfigParser()
ansible_config.add_section('defaults')
ansible_config.set('defaults', 'internal_poll_interval', '0.05')
with open(ansible_cfg_file, 'w') as f:
ansible_config.write(f)
env['ANSIBLE_CONFIG'] = ansible_cfg_file
elif 'ANSIBLE_CONFIG' not in env and ansible_cfg_file:
env['ANSIBLE_CONFIG'] = ansible_cfg_file
if log_path:
env['VALIDATIONS_LOG_DIR'] = log_path
@ -434,7 +452,6 @@ class Ansible(object):
if not BACKWARD_COMPAT:
r_opts.update({
'envvars': envvars,
'project_dir': playbook_dir,
'fact_cache': ansible_artifact_path,
'fact_cache_type': 'jsonfile'
@ -453,6 +470,16 @@ class Ansible(object):
if parallel_run:
r_opts['directory_isolation_base_path'] = ansible_artifact_path
if config:
if 'ansible_runner' in config.keys():
r_opts.update(config['ansible_runner'])
if 'ansible_environment' in config.keys():
envvars.update(config['ansible_environment'])
self._dump_validation_config(config, ansible_artifact_path)
if not BACKWARD_COMPAT:
r_opts.update({'envvars': envvars})
runner_config = ansible_runner.runner_config.RunnerConfig(**r_opts)
runner_config.prepare()
runner_config.env['ANSIBLE_STDOUT_CALLBACK'] = \

View File

@ -14,11 +14,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
from cliff.app import App
from cliff.commandmanager import CommandManager
from validations_libs import utils
class ValidationCliApp(App):
"""Cliff application for the `ValidationCli` tool.
@ -50,6 +53,23 @@ class ValidationCliApp(App):
if err:
self.LOG.debug('got an error: {}'.format(err))
def set_argument_parser(self, parser, section='default'):
""" Set Arguments parser depending of the precedence ordering:
* User CLI arguments
* Configuration file
* Default CLI values
"""
args, _ = parser.parse_known_args()
config = utils.load_config(os.path.abspath(args.config))
config_args = config.get(section, {})
for key, value in args._get_kwargs():
if parser.get_default(key) != value:
config_args.update({key: value})
elif key not in config.keys():
config_args.update({key: value})
parser.set_defaults(**config_args)
return parser
def main(argv=sys.argv[1:]):
v_cli = ValidationCliApp()

View File

@ -14,10 +14,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from cliff import _argparse
from cliff.command import Command
from cliff.lister import Lister
from cliff.show import ShowOne
from validations_libs import constants
from validations_libs import utils
class BaseCommand(Command):
@ -34,6 +39,14 @@ class BaseCommand(Command):
)
for hook in self._hooks:
hook.obj.get_parser(parser)
parser.add_argument(
'--config',
dest='config',
default=constants.VALIDATION_CONFIG_FILE,
help=("Config file path for Validation.")
)
return parser
@ -54,4 +67,27 @@ class BaseLister(Lister):
for action in parser._actions:
vf_parser._add_action(action)
vf_parser.add_argument(
'--config',
dest='config',
default=constants.VALIDATION_CONFIG_FILE,
help=("Config file path for Validation.")
)
return vf_parser
class BaseShow(ShowOne):
"""Base Show client implementation class"""
def get_parser(self, parser):
"""Argument parser for base show"""
parser = super(BaseShow, self).get_parser(parser)
parser.add_argument(
'--config',
dest='config',
default=constants.VALIDATION_CONFIG_FILE,
help=("Config file path for Validation.")
)
return parser

View File

@ -46,27 +46,26 @@ class ListHistory(BaseLister):
default=constants.VALIDATIONS_LOG_BASEDIR,
help=("Path where the validation log files "
"is located."))
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
validation_log_dir = parsed_args.validation_log_dir
history_limit = parsed_args.history_limit
if parsed_args.history_limit < 1:
raise ValueError(
(
"Number <n> of the most recent runs must be > 0. "
"You have provided {}").format(
parsed_args.history_limit))
if history_limit < 1:
msg = ("Number <n> of the most recent runs must be > 0. "
"You have provided {}").format(history_limit)
raise ValueError(msg)
self.app.LOG.info(
(
"Limiting output to the maximum of "
"{} last validations.").format(
parsed_args.history_limit))
("Limiting output to the maximum of "
"{} last validations.").format(history_limit))
actions = ValidationActions(parsed_args.validation_log_dir)
actions = ValidationActions(validation_log_dir)
return actions.show_history(
validation_ids=parsed_args.validation,
history_limit=parsed_args.history_limit)
history_limit=history_limit)
class GetHistory(BaseCommand):
@ -87,10 +86,10 @@ class GetHistory(BaseCommand):
default=constants.VALIDATIONS_LOG_BASEDIR,
help=("Path where the validation log files "
"is located."))
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
self.app.LOG.debug(
(
"Obtaining information about the validation run {}\n"

View File

@ -14,14 +14,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from cliff.lister import Lister
import json
import sys
from validations_libs.validation_actions import ValidationActions
from validations_libs import constants
from validations_libs.cli.base import BaseLister
from validations_libs.cli.parseractions import CommaListAction
class ValidationList(Lister):
class ValidationList(BaseLister):
"""Validation List client implementation class"""
def get_parser(self, parser):
@ -40,13 +42,13 @@ class ValidationList(Lister):
default=constants.ANSIBLE_VALIDATION_DIR,
help=("Path where the validation playbooks "
"are located."))
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
group = parsed_args.group
validation_dir = parsed_args.validation_dir
group = parsed_args.group
v_actions = ValidationActions(validation_path=validation_dir)
return (v_actions.list_validations(group))

View File

@ -15,9 +15,11 @@
# under the License.
import getpass
import os
import sys
from validations_libs import constants
from validations_libs import utils
from validations_libs.validation_actions import ValidationActions
from validations_libs.cli import common
from validations_libs.cli.base import BaseCommand
@ -139,13 +141,15 @@ class Run(BaseCommand):
"--group pre-upgrade,prep | "
"--group openshift-on-openstack"))
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
v_actions = ValidationActions(
validation_path=parsed_args.validation_dir)
# Load config:
config = utils.load_config(os.path.abspath(parsed_args.config))
v_actions = ValidationActions(parsed_args.validation_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
@ -176,8 +180,8 @@ 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
)
log_path=parsed_args.validation_log_dir,
config=config)
except RuntimeError as e:
raise RuntimeError(e)

View File

@ -14,14 +14,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from cliff.show import ShowOne
import json
import sys
from validations_libs.validation_actions import ValidationActions
from validations_libs import constants
from validations_libs.cli.parseractions import CommaListAction
from validations_libs.cli.base import BaseShow
class Show(ShowOne):
class Show(BaseShow):
"""Validation Show client implementation class"""
def get_parser(self, parser):
@ -35,7 +37,8 @@ class Show(ShowOne):
metavar="<validation>",
type=str,
help="Show a specific validation.")
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
@ -50,7 +53,7 @@ class Show(ShowOne):
return data.keys(), data.values()
class ShowGroup(ShowOne):
class ShowGroup(BaseShow):
"""Validation Show group client implementation class"""
def get_parser(self, parser):
@ -64,7 +67,8 @@ class ShowGroup(ShowOne):
metavar='<group_name>',
dest="group",
help=("Show a specific group."))
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
"""Take validation action"""
@ -76,7 +80,7 @@ class ShowGroup(ShowOne):
return v_actions.group_information(group)
class ShowParameter(ShowOne):
class ShowParameter(BaseShow):
"""Display Validations Parameters"""
def get_parser(self, parser):
@ -132,11 +136,13 @@ class ShowParameter(ShowOne):
help=("Print representation of the validation. "
"The choices of the output format is json,yaml. ")
)
return parser
# Merge config and CLI args:
return self.app.set_argument_parser(parser)
def take_action(self, parsed_args):
v_actions = ValidationActions(parsed_args.validation_dir)
validation_dir = parsed_args.validation_dir
v_actions = ValidationActions(validation_dir)
params = v_actions.show_validations_parameters(
parsed_args.validation_name,
parsed_args.group,

View File

@ -38,3 +38,9 @@ VALIDATIONS_LOG_BASEDIR = os.path.expanduser('~/validations')
VALIDATION_ANSIBLE_ARTIFACT_PATH = os.path.join(
VALIDATIONS_LOG_BASEDIR,
'artifacts')
ANSIBLE_RUNNER_CONFIG_PARAMETERS = ['verbosity', 'extravars', 'fact_cache',
'fact_cache_type', 'inventory', 'playbook',
'project_dir', 'quiet', 'rotate_artifacts']
VALIDATION_CONFIG_FILE = "/etc/validation.cfg"

View File

@ -22,6 +22,12 @@ from unittest import TestCase
from validations_libs.cli import app
class FakeValidationApp(app.ValidationCliApp):
def set_argument_parser(self, parser):
return parser
class BaseCommand(TestCase):
def check_parser(self, cmd, args, verify_args):
@ -39,7 +45,8 @@ class BaseCommand(TestCase):
def setUp(self):
super(BaseCommand, self).setUp()
self.app = app.ValidationCliApp()
self.app = FakeValidationApp()
KEYVALUEACTION_VALUES = {
'valid': 'foo=bar',

View File

@ -79,7 +79,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars', 'key=value']
@ -111,7 +113,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars', 'key=value1',
@ -156,7 +160,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars-file', '/foo/vars.yaml']
@ -186,7 +192,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-env-vars', 'key=value']
@ -220,7 +228,9 @@ class TestRun(BaseCommand):
'extra_env_vars': {'ANSIBLE_STDOUT_CALLBACK': 'default'},
'python_interpreter': sys.executable,
'quiet': False,
'ssh_user': 'doe'}
'ssh_user': 'doe',
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-env-vars', 'ANSIBLE_STDOUT_CALLBACK=default']
@ -250,7 +260,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-env-vars', 'key=value1',
@ -284,7 +296,9 @@ class TestRun(BaseCommand):
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe',
'log_path': mock_log_dir}
'log_path': mock_log_dir,
'config': {}
}
arglist = ['--validation', 'foo',
'--extra-vars', 'key=value',
@ -323,7 +337,9 @@ class TestRun(BaseCommand):
'extra_env_vars': {'key2': 'value2'},
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe'}
'ssh_user': 'doe',
'config': {}
}
arglist = ['--validation', 'foo']
verifylist = [('validation_name', ['foo'])]
@ -347,10 +363,77 @@ class TestRun(BaseCommand):
'extra_env_vars': {'key2': 'value2'},
'python_interpreter': sys.executable,
'quiet': True,
'ssh_user': 'doe'}
'ssh_user': 'doe',
'config': {}
}
arglist = ['--validation', 'foo']
verifylist = [('validation_name', ['foo'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(RuntimeError, self.cmd.take_action, parsed_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=fakes.FAKE_SUCCESS_RUN)
def test_run_with_wrong_config(self, mock_run, mock_user, mock_log_dir):
arglist = ['--validation', 'foo', '--config', 'wrong.cfg']
verifylist = [('validation_name', ['foo']),
('config', 'wrong.cfg')]
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'group': [],
'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,
'config': {}
}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**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=fakes.FAKE_SUCCESS_RUN)
@mock.patch('os.path.exists', return_value=True)
def test_run_with_config(self, mock_exists,
mock_run, mock_user,
mock_log_dir):
arglist = ['--validation', 'foo', '--config', 'config.cfg']
verifylist = [('validation_name', ['foo']),
('config', 'config.cfg')]
run_called_args = {
'inventory': 'localhost',
'limit_hosts': None,
'group': [],
'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,
'config': {}
}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_run.assert_called_with(**run_called_args)

View File

@ -299,6 +299,22 @@ FAKE_FAILED_RUN = [{'Duration': '0:00:01.761',
FAKE_VALIDATIONS_PATH = '/usr/share/ansible/validation-playbooks'
DEFAULT_CONFIG = {'validation_dir': '/usr/share/ansible/validation-playbooks',
'validation_log_dir': 'validations',
'ansible_base_dir': '/usr/share/ansible/',
'ssh_user': 'stack',
'output_log': 'output.log',
'history_limit': 15}
ANSIBLE_RUNNER_CONFIG = {'verbosity': 5, 'extravars': {'minimal_ram_gb': 2},
'fact_cache_type': 'jsonfile',
'project_dir':
'/usr/share/ansible/validation-playbooks',
'quiet': True, 'rotate_artifacts': 256}
ANSIBLE_ENVIRONNMENT_CONFIG = {'ANSIBLE_TIMEOUT': '30',
'ANSIBLE_HOST_KEY_CHECKING': 'False'}
def fake_ansible_runner_run_return(status='successful', rc=0):
return status, rc

View File

@ -308,3 +308,40 @@ class TestUtils(TestCase):
"""Test if failure to create artifacts dir raises 'RuntimeError'.
"""
self.assertRaises(RuntimeError, utils.create_artifacts_dir, "/foo/bar")
def test_eval_types_str(self):
self.assertIsInstance(utils._eval_types('/usr'), str)
def test_eval_types_bool(self):
self.assertIsInstance(utils._eval_types('True'), bool)
def test_eval_types_int(self):
self.assertIsInstance(utils._eval_types('15'), int)
def test_eval_types_dict(self):
self.assertIsInstance(utils._eval_types('{}'), dict)
@mock.patch('os.path.exists', return_value=True)
@mock.patch('configparser.ConfigParser.sections',
return_value=['default'])
def test_load_config(self, mock_config, mock_exists):
results = utils.load_config('foo.cfg')
self.assertEqual(results, {})
def test_default_load_config(self):
results = utils.load_config('validation.cfg')
self.assertEqual(results['default'], fakes.DEFAULT_CONFIG)
def test_ansible_runner_load_config(self):
results = utils.load_config('validation.cfg')
self.assertEqual(results['ansible_runner'],
fakes.ANSIBLE_RUNNER_CONFIG)
def test_ansible_environment_config_load_config(self):
results = utils.load_config('validation.cfg')
self.assertEqual(
results['ansible_environment']['ANSIBLE_HOST_KEY_CHECKING'],
fakes.ANSIBLE_ENVIRONNMENT_CONFIG['ANSIBLE_HOST_KEY_CHECKING'])
self.assertEqual(
results['ansible_environment']['ANSIBLE_TIMEOUT'],
fakes.ANSIBLE_ENVIRONNMENT_CONFIG['ANSIBLE_TIMEOUT'])

View File

@ -100,13 +100,14 @@ class TestValidationActions(TestCase):
'extra_vars': None,
'limit_hosts': '!cloud1',
'extra_env_variables': None,
'ansible_cfg': None,
'ansible_cfg_file': None,
'gathering_policy': 'explicit',
'ansible_artifact_path': '/var/log/validations/artifacts/123_fake.yaml_time',
'log_path': '/var/log/validations',
'run_async': False,
'python_interpreter': None,
'ssh_user': None
'ssh_user': None,
'config': None
}
playbook = ['fake.yaml']
@ -160,13 +161,14 @@ class TestValidationActions(TestCase):
'extra_vars': None,
'limit_hosts': '!cloud1,cloud,!cloud2',
'extra_env_variables': None,
'ansible_cfg': None,
'ansible_cfg_file': None,
'gathering_policy': 'explicit',
'ansible_artifact_path': '/var/log/validations/artifacts/123_fake.yaml_time',
'log_path': '/var/log/validations',
'run_async': False,
'python_interpreter': None,
'ssh_user': None
'ssh_user': None,
'config': None
}
playbook = ['fake.yaml']

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import ast
import configparser
import datetime
import glob
import logging
@ -410,3 +412,60 @@ def convert_data(data=''):
raise TypeError("The input data should be either a List or a String")
else:
return data
def _eval_types(value):
try:
return int(value)
except ValueError:
pass
try:
return ast.literal_eval(value)
except (SyntaxError, NameError, ValueError):
pass
try:
return str(value)
except ValueError:
msg = ("Can not eval or type not supported for value: {},").format(
value)
raise ValueError(msg)
def load_config(config):
"""Load Config File from CLI"""
if not os.path.exists(config):
msg = ("Config file {} could not be found, ignoring...").format(config)
LOG.warning(msg)
return {}
parser = configparser.ConfigParser()
parser.optionxform = str
parser.read(config)
data = {}
try:
for section in parser.sections():
for keys, values in parser.items(section):
if section not in data:
# Init section in dictionary
data[section] = {}
if section == 'ansible_environment':
# for Ansible environment variables we dont want to cast
# types, each values should a type String.
data[section][keys] = values
elif section == 'ansible_runner' and \
keys not in constants.ANSIBLE_RUNNER_CONFIG_PARAMETERS:
# for Ansible runner parameters, we select only a set
# of parameters which will be passed as **kwargs in the
# runner, so we have to ignore all the others.
continue
msg = ("Incompatible key found for ansible_runner section {}, "
"ignoring {} ...").format(section, keys)
LOG.warning(msg)
else:
data[section][keys] = _eval_types(values)
except configparser.NoSectionError:
msg = ("Wrong format for the config file {}, "
"section {} can not be found, ignoring...").format(config,
section)
LOG.warning(msg)
return {}
return data

View File

@ -238,7 +238,7 @@ class ValidationActions(object):
skip_list=None,
callback_whitelist=None,
output_callback='validation_stdout',
ssh_user=None):
ssh_user=None, config=None):
"""Run one or multiple validations by name(s) or by group(s)
:param validation_name: A list of validation names
@ -302,6 +302,9 @@ class ValidationActions(object):
:rtype: ``list``
:param ssh_user: Ssh user for Ansible remote connection
:type ssh_user: ``string``
:param config: A dictionary of configuration for Validation loaded from
an validation.cfg file.
:type config: ``dict``
:Example:
@ -387,13 +390,14 @@ class ValidationActions(object):
extra_vars=extra_vars,
limit_hosts=_hosts,
extra_env_variables=extra_env_vars,
ansible_cfg=ansible_cfg,
ansible_cfg_file=ansible_cfg,
gathering_policy='explicit',
ansible_artifact_path=artifacts_dir,
log_path=log_path,
run_async=run_async,
python_interpreter=python_interpreter,
ssh_user=ssh_user)
ssh_user=ssh_user,
config=config)
results.append({'playbook': _playbook,
'rc_code': _rc,
'status': _status,