Implement validations-libs framework for ALL validator commands

Implement All command with validations libs framework

Change-Id: I0443da140020540917396cdfe00da8ac29d20297
This commit is contained in:
Mathieu Bultel 2020-03-24 13:55:04 +01:00
parent f66b42757b
commit 6dee93a9e7
6 changed files with 131 additions and 626 deletions

View File

@ -149,6 +149,7 @@ traceback2==1.4.0
tripleo-common==12.4.0 tripleo-common==12.4.0
ujson==1.35 ujson==1.35
unittest2==1.1.0 unittest2==1.1.0
validations-libs==1.0.0
vine==1.1.4 vine==1.1.4
voluptuous==0.8.9 voluptuous==0.8.9
waitress==1.1.0 waitress==1.1.0

View File

@ -18,3 +18,4 @@ websocket-client>=0.44.0 # LGPLv2+
tripleo-common>=12.4.0 # Apache-2.0 tripleo-common>=12.4.0 # Apache-2.0
cryptography>=2.1 # BSD/Apache-2.0 cryptography>=2.1 # BSD/Apache-2.0
ansible-runner>=1.4.4 # Apache 2.0 ansible-runner>=1.4.4 # Apache 2.0
validations-libs>=1.0.0

View File

@ -1590,51 +1590,6 @@ class TestGetLocalTimezone(TestCase):
self.assertEqual('UTC', utils.get_local_timezone()) self.assertEqual('UTC', utils.get_local_timezone())
class TestGetParamFieldName(TestCase):
def test_with_empty_val_data(self):
input_parameter = {}
expected = "parameters"
result = utils.get_param_field_name(input_parameter)
self.assertEqual(result, expected)
def test_with_val_data_and_returns_parameters(self):
input_parameter = {'validations': [
{'description': 'validation number one',
'groups': ['prep', 'pre-deployment'],
'id': 'Validation_Number_One',
'name': 'Validation Number One',
'parameters': {}},
{'description': 'validation number two',
'groups': ['post-deployment'],
'id': 'Validation_Number_Two',
'name': 'Validation Number Two',
'parameters': {'config_file': "/etc/config.conf"}},
]}
expected = "parameters"
result = utils.get_param_field_name(input_parameter)
self.assertEqual(result, expected)
def test_with_val_data_and_returns_metadata(self):
input_parameter = {'validations': [
{'description': 'validation number one',
'groups': ['prep', 'pre-deployment'],
'id': 'Validation_Number_One',
'name': 'Validation Number One',
'metadata': {}},
{'description': 'validation number two',
'groups': ['post-deployment'],
'id': 'Validation_Number_Two',
'name': 'Validation Number Two',
'metadata': {'config_file': "/etc/config.conf"}},
]}
expected = "metadata"
result = utils.get_param_field_name(input_parameter)
self.assertEqual(result, expected)
class TestParseExtraVars(TestCase): class TestParseExtraVars(TestCase):
def test_simple_case_text_format(self): def test_simple_case_text_format(self):
input_parameter = ['key1=val1', 'key2=val2 key3=val3'] input_parameter = ['key1=val1', 'key2=val2 key3=val3']

View File

@ -151,8 +151,8 @@ class TestValidatorGroupInfo(utils.TestCommand):
# Get the command object to test # Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorGroupInfo(self.app, None) self.cmd = tripleo_validator.TripleOValidatorGroupInfo(self.app, None)
@mock.patch('tripleoclient.utils.prepare_validation_groups_for_display', @mock.patch('validations_libs.validation_actions.ValidationActions.'
return_value=GROUPS_LIST) 'group_information', return_value=GROUPS_LIST)
def test_show_group_info(self, mock_validations): def test_show_group_info(self, mock_validations):
arglist = [] arglist = []
verifylist = [] verifylist = []
@ -170,7 +170,8 @@ class TestValidatorList(utils.TestCommand):
# Get the command object to test # Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorList(self.app, None) self.cmd = tripleo_validator.TripleOValidatorList(self.app, None)
@mock.patch('tripleoclient.utils.parse_all_validations_on_disk', @mock.patch('validations_libs.validation_actions.ValidationActions.'
'list_validations',
return_value=VALIDATIONS_LIST) return_value=VALIDATIONS_LIST)
def test_validation_list_noargs(self, mock_validations): def test_validation_list_noargs(self, mock_validations):
arglist = [] arglist = []
@ -189,8 +190,9 @@ class TestValidatorShow(utils.TestCommand):
# Get the command object to test # Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorShow(self.app, None) self.cmd = tripleo_validator.TripleOValidatorShow(self.app, None)
@mock.patch('tripleoclient.utils.parse_all_validations_on_disk', @mock.patch('validations_libs.validation_actions.ValidationActions.'
return_value=VALIDATIONS_LIST) 'show_validations',
return_value=VALIDATIONS_LIST[0])
def test_validation_show(self, mock_validations): def test_validation_show(self, mock_validations):
arglist = ['my_val1'] arglist = ['my_val1']
verifylist = [('validation_id', 'my_val1')] verifylist = [('validation_id', 'my_val1')]
@ -209,8 +211,9 @@ class TestValidatorShowParameter(utils.TestCommand):
self.cmd = tripleo_validator.TripleOValidatorShowParameter(self.app, self.cmd = tripleo_validator.TripleOValidatorShowParameter(self.app,
None) None)
@mock.patch('tripleoclient.utils.parse_all_validations_on_disk', @mock.patch('validations_libs.validation_actions.ValidationActions.'
return_value=VALIDATIONS_LIST) 'show_validations_parameters',
return_value=VALIDATIONS_LIST[1])
def test_validation_show_parameter(self, mock_validations): def test_validation_show_parameter(self, mock_validations):
arglist = ['--validation', 'my_val2'] arglist = ['--validation', 'my_val2']
verifylist = [('validation_name', ['my_val2'])] verifylist = [('validation_name', ['my_val2'])]
@ -229,7 +232,8 @@ class TestValidatorShowRun(utils.TestCommand):
self.cmd = tripleo_validator.TripleOValidatorShowRun(self.app, self.cmd = tripleo_validator.TripleOValidatorShowRun(self.app,
None) None)
@mock.patch('tripleoclient.utils.parse_all_validations_logs_on_disk', @mock.patch('validations_libs.validation_actions.ValidationLogs.'
'get_logfile_content_by_uuid',
return_value=VALIDATIONS_LOGS_CONTENTS_LIST) return_value=VALIDATIONS_LOGS_CONTENTS_LIST)
def test_validation_show_run(self, mock_validations): def test_validation_show_run(self, mock_validations):
arglist = ['008886df-d297-1eaa-2a74-000000000008'] arglist = ['008886df-d297-1eaa-2a74-000000000008']
@ -249,7 +253,8 @@ class TestValidatorShowHistory(utils.TestCommand):
self.cmd = tripleo_validator.TripleOValidatorShowHistory(self.app, self.cmd = tripleo_validator.TripleOValidatorShowHistory(self.app,
None) None)
@mock.patch('tripleoclient.utils.parse_all_validations_logs_on_disk', @mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history',
return_value=VALIDATIONS_LOGS_CONTENTS_LIST) return_value=VALIDATIONS_LOGS_CONTENTS_LIST)
def test_validation_show_history(self, mock_validations): def test_validation_show_history(self, mock_validations):
arglist = [] arglist = []
@ -259,7 +264,8 @@ class TestValidatorShowHistory(utils.TestCommand):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@mock.patch('tripleoclient.utils.parse_all_validations_logs_on_disk', @mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history',
return_value=VALIDATIONS_LOGS_CONTENTS_LIST) return_value=VALIDATIONS_LOGS_CONTENTS_LIST)
def test_validation_show_history_for_a_validation(self, mock_validations): def test_validation_show_history_for_a_validation(self, mock_validations):
arglist = [ arglist = [

View File

@ -30,7 +30,6 @@ import hashlib
import logging import logging
from six.moves.configparser import ConfigParser from six.moves.configparser import ConfigParser
import json
import multiprocessing import multiprocessing
import netaddr import netaddr
import os import os
@ -2191,200 +2190,6 @@ def _get_from_cfg(cfg, accessor, param, section):
return val return val
def get_validation_metadata(validation, key):
default_metadata = {
'name': 'Unnamed',
'description': 'No description',
'stage': 'No stage',
'groups': [],
}
try:
return validation[0]['vars']['metadata'].get(key,
default_metadata[key])
except KeyError:
LOG.exception(_("Key '{key}' not even found in "
"default metadata").format(key=key))
except TypeError:
LOG.exception(_("Failed to get validation metadata."))
def get_validation_parameters(validation):
try:
return {
k: v
for k, v in validation[0]['vars'].items()
if k != 'metadata'
}
except KeyError:
LOG.debug(_("No parameters found for this validation"))
return dict()
def read_validation_groups_file(groups_file_path=None):
"""Load groups.yaml file and return a dictionary with its contents"""
if not groups_file_path:
groups_file_path = constants.VALIDATION_GROUPS_INFO
if not os.path.exists(groups_file_path):
return []
with open(groups_file_path, 'r') as grps:
contents = yaml.safe_load(grps)
return contents
def get_validation_group_name_list():
"""Get the validation group name list only"""
results = []
groups = read_validation_groups_file()
if groups and isinstance(groups, dict):
for grp_name in six.viewkeys(groups):
results.append(grp_name)
return results
def prepare_validation_groups_for_display():
results = []
groups = read_validation_groups_file()
for grp_name, grp_desc in sorted(groups.items()):
results.append((grp_name, grp_desc[0].get('description')))
return results
def parse_all_validations_on_disk(path, groups=None):
results = []
validations_abspath = glob.glob("{path}/*.yaml".format(path=path))
if isinstance(groups, six.string_types):
group_list = []
group_list.append(groups)
groups = group_list
for pl in validations_abspath:
validation_id, _ext = os.path.splitext(os.path.basename(pl))
with open(pl, 'r') as val_playbook:
contents = yaml.safe_load(val_playbook)
validation_groups = get_validation_metadata(contents, 'groups') or []
if not groups or set.intersection(set(groups), set(validation_groups)):
results.append({
'id': validation_id,
'name': get_validation_metadata(contents, 'name'),
'groups': get_validation_metadata(contents, 'groups'),
'description': get_validation_metadata(contents,
'description'),
'parameters': get_validation_parameters(contents)
})
return results
def get_param_field_name(validations_data=None):
"""Get the current parameters field name in a Dict
Returns either 'parameters' or 'metadata'.
By Default, it returns 'parameters'.
"""
# TODO(gchamoul): Added for backwards compatibility and will be
# removed for Train release.
if validations_data is None:
validations_data = {}
if 'metadata' in validations_data.get('validations', [[]])[0]:
return 'metadata'
return 'parameters'
def get_validations_parameters(validations_data,
validation_name=None,
groups=None):
if validation_name is None:
validation_name = []
if groups is None:
groups = []
params = {}
param_field_name = get_param_field_name(validations_data)
for val in validations_data['validations']:
wanted_validation = False
wanted_group = False
if val.get('id') in validation_name:
wanted_validation = True
for grp in groups:
if grp in val.get('groups'):
wanted_group = True
if wanted_validation or wanted_group:
params[val.get('id')] = {
'parameters': val.get(param_field_name)
}
return params
def get_validations_json(validations_data):
"""Return the validations information as a pretty printed json """
return json.dumps(validations_data, indent=4, sort_keys=True)
def get_validations_yaml(validations_data):
"""Return the validations information as a pretty printed yaml """
return yaml.safe_dump(validations_data,
allow_unicode=True,
default_flow_style=False,
indent=2)
def get_new_validations_logs_on_disk():
"""Return a list of new log execution filenames """
files = []
for root, dirs, filenames in os.walk(constants.VALIDATIONS_LOG_BASEDIR):
files = [
f for f in filenames if not f.startswith('processed')
and os.path.splitext(f)[1] == '.json'
]
return files
def parse_all_validations_logs_on_disk(uuid_run=None, validation_id=None):
results = []
path = constants.VALIDATIONS_LOG_BASEDIR
logfile = "{}/*.json".format(path)
if validation_id:
logfile = "{}/*_{}_*.json".format(path, validation_id)
if uuid_run:
logfile = "{}/*_{}_*.json".format(path, uuid_run)
logfiles_path = glob.glob(logfile)
for logfile_path in logfiles_path:
with open(logfile_path, 'r') as log:
contents = json.load(log)
results.append(contents)
return results
def indent(text):
'''Indent the given text by four spaces.'''
return ''.join(' {}\n'.format(line) for line in text.splitlines())
def get_local_timezone(): def get_local_timezone():
info = run_command(['timedatectl'], name='timedatectl') info = run_command(['timedatectl'], name='timedatectl')
timezoneline = [tz for tz in info.split('\n') if 'Time zone:' in tz] timezoneline = [tz for tz in info.split('\n') if 'Time zone:' in tz]

View File

@ -16,12 +16,7 @@
import argparse import argparse
import json import json
import logging import logging
import os
import six
import textwrap
import time
from concurrent.futures import ThreadPoolExecutor
from osc_lib import exceptions from osc_lib import exceptions
from osc_lib.i18n import _ from osc_lib.i18n import _
from prettytable import PrettyTable from prettytable import PrettyTable
@ -30,10 +25,16 @@ from tripleoclient import command
from tripleoclient import constants from tripleoclient import constants
from tripleoclient import utils as oooutils from tripleoclient import utils as oooutils
from validations_libs import constants as v_consts
from validations_libs import utils as v_utils
from validations_libs.validation_actions import ValidationActions
from validations_libs.validation_logs import ValidationLogs
LOG = logging.getLogger(__name__ + ".TripleoValidator") LOG = logging.getLogger(__name__ + ".TripleoValidator")
RED = "\033[1;31m" RED = "\033[1;31m"
GREEN = "\033[0;32m" GREEN = "\033[0;32m"
CYAN = "\033[36m"
RESET = "\033[0;0m" RESET = "\033[0;0m"
FAILED_VALIDATION = "{}FAILED{}".format(RED, RESET) FAILED_VALIDATION = "{}FAILED{}".format(RED, RESET)
@ -44,7 +45,7 @@ GROUP_FILE = constants.VALIDATION_GROUPS_INFO
class _CommaListGroupAction(argparse.Action): class _CommaListGroupAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
opts = oooutils.get_validation_group_name_list() opts = v_utils.get_validation_group_name_list(GROUP_FILE)
for value in values.split(','): for value in values.split(','):
if value not in opts: if value not in opts:
message = ("Invalid choice: {value} (choose from {choice})" message = ("Invalid choice: {value} (choose from {choice})"
@ -67,20 +68,8 @@ class TripleOValidatorGroupInfo(command.Lister):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
group = oooutils.prepare_validation_groups_for_display() actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
return actions.group_information(GROUP_FILE)
if not group:
raise exceptions.CommandError(
"Could not find groups information file %s" % GROUP_FILE)
group_info = []
for gp in group:
validations = oooutils.parse_all_validations_on_disk(
constants.ANSIBLE_VALIDATION_DIR, gp[0])
group_info.append((gp[0], gp[1], len(validations)))
column_name = ("Groups", "Description", "Number of Validations")
return (column_name, group_info)
class TripleOValidatorShow(command.ShowOne): class TripleOValidatorShow(command.ShowOne):
@ -97,83 +86,14 @@ class TripleOValidatorShow(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
validation = self.get_validations_details(parsed_args.validation_id) LOG.debug(_('Show validation result'))
logfile_contents = oooutils.parse_all_validations_logs_on_disk( try:
validation_id=parsed_args.validation_id) actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
data = actions.show_validations(parsed_args.validation_id)
if not validation: return data.keys(), data.values()
raise exceptions.CommandError( except Exception as e:
"Could not find validation %s" % parsed_args.validation_id) raise RuntimeError(_("Validations listing finished with errors\n"
"Output: {}").format(e))
return self.format_validation(validation, logfile_contents)
def get_validations_details(self, validation):
results = oooutils.parse_all_validations_on_disk(
constants.ANSIBLE_VALIDATION_DIR)
for r in results:
if r['id'] == validation:
return r
return []
def format_validation(self, validation, logfile):
column_names = ["ID"]
data = [validation.pop('id')]
if 'name' in validation:
column_names.append("Name")
data.append(validation.pop('name'))
if 'description' in validation:
column_names.append("Description")
data.append(textwrap.fill(validation.pop('description')))
if 'groups' in validation:
column_names.append("Groups")
data.append(", ".join(validation.pop('groups')))
other_fields = list(validation.keys())
other_fields.sort()
for field in other_fields:
column_names.append(field.capitalize())
data.append(validation[field])
# history, stats ...
total_number = 0
failed_number = 0
passed_number = 0
last_execution = None
dates = []
if logfile:
total_number = len(logfile)
for run in logfile:
if 'validation_output' in run and run.get('validation_output'):
failed_number += 1
else:
passed_number += 1
date_time = \
run['plays'][0]['play']['duration'].get('start').split('T')
date_start = date_time[0]
time_start = date_time[1].split('Z')[0]
newdate = \
time.strptime(date_start + time_start, '%Y-%m-%d%H:%M:%S.%f')
dates.append(newdate)
if dates:
last_execution = time.strftime('%Y-%m-%d %H:%M:%S', max(dates))
column_names.append("Number of execution")
data.append("Total: {}, Passed: {}, Failed: {}".format(total_number,
passed_number,
failed_number))
column_names.append("Last execution date")
data.append(last_execution)
return column_names, data
class TripleOValidatorShowParameter(command.Command): class TripleOValidatorShowParameter(command.Command):
@ -216,14 +136,12 @@ class TripleOValidatorShowParameter(command.Command):
parser.add_argument( parser.add_argument(
'--download', '--download',
metavar=('[json|yaml]', '/tmp/myvars'),
action='store', action='store',
default=[], default=None,
nargs=2,
help=_("Create a json or a yaml file " help=_("Create a json or a yaml file "
"containing all the variables " "containing all the variables "
"available for the validations: " "available for the validations: "
"[yaml|json] /tmp/myvars") "/tmp/myvars")
) )
parser.add_argument( parser.add_argument(
@ -238,65 +156,18 @@ class TripleOValidatorShowParameter(command.Command):
return parser return parser
def _create_variables_file(self, data, varsfile):
msg = (_("The file %s already exists on the filesystem, "
"do you still want to continue [y/N] "))
if varsfile[0] not in ['json', 'yaml']:
raise RuntimeError(_('Wrong file type: %s') % varsfile[0])
else:
LOG.debug(_('Launch variables file creation'))
try:
if os.path.exists(varsfile[-1]):
confirm = oooutils.prompt_user_for_confirmation(
message=msg % varsfile[-1], logger=LOG)
if not confirm:
raise RuntimeError(_("Action not confirmed, exiting"))
with open(varsfile[-1], 'w') as f:
params = {}
for val_name in list(data.keys()):
for k, v in data[val_name].get('parameters').items():
params[k] = v
if varsfile[0] == 'json':
f.write(oooutils.get_validations_json(params))
elif varsfile[0] == 'yaml':
f.write(oooutils.get_validations_yaml(params))
print(
_('The file %s has been created successfully') %
varsfile[-1])
except Exception as e:
print(_("Creating variables file finished with errors"))
print('Output: {}'.format(e))
def _run_validator_show_parameter(self, parsed_args):
LOG.debug(_('Launch showing parameters for the validations'))
try:
validations = oooutils.parse_all_validations_on_disk(
constants.ANSIBLE_VALIDATION_DIR)
out = oooutils.get_validations_parameters(
{'validations': validations},
parsed_args.validation_name,
parsed_args.group
)
if parsed_args.download:
self._create_variables_file(out,
parsed_args.download)
else:
if parsed_args.format == 'yaml':
print(oooutils.get_validations_yaml(out))
else:
print(oooutils.get_validations_json(out))
except Exception as e:
raise RuntimeError(_("Validations Show Parameters "
"finished with errors\n"
"Output: {}").format(e))
def take_action(self, parsed_args): def take_action(self, parsed_args):
self._run_validator_show_parameter(parsed_args) actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
params = actions.show_validations_parameters(
parsed_args.validation_name,
parsed_args.group,
parsed_args.format,
parsed_args.download)
if parsed_args.download:
print("The file {} has been created successfully").format(
parsed_args.download)
else:
print(params)
class TripleOValidatorList(command.Lister): class TripleOValidatorList(command.Lister):
@ -322,16 +193,11 @@ class TripleOValidatorList(command.Lister):
def take_action(self, parsed_args): def take_action(self, parsed_args):
LOG.debug(_('Launch listing the validations')) LOG.debug(_('Launch listing the validations'))
try: try:
validations = oooutils.parse_all_validations_on_disk( v_consts.DEFAULT_VALIDATIONS_BASEDIR = constants.\
constants.ANSIBLE_VALIDATION_DIR, parsed_args.group) DEFAULT_VALIDATIONS_BASEDIR
actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR,
return_values = [] parsed_args.group)
column_name = ('ID', 'Name', 'Groups') return actions.list_validations()
for val in validations:
return_values.append((val.get('id'), val.get('name'),
", ".join(val.get('groups'))))
return (column_name, return_values)
except Exception as e: except Exception as e:
raise RuntimeError(_("Validations listing finished with errors\n" raise RuntimeError(_("Validations listing finished with errors\n"
"Output: {}").format(e)) "Output: {}").format(e))
@ -356,15 +222,29 @@ class TripleOValidatorRun(command.Command):
) )
parser.add_argument( parser.add_argument(
'--workers', '-w', '--quiet',
metavar='N', action='store',
dest='workers', default=False,
default=1, help=_(
type=int, "Run Ansible in silent mode."
help=_("The maximum number of threads that can " )
"be used to execute the given validations")
) )
parser.add_argument(
'--limit', action='store', required=False, help=_(
"A string that identifies a single node or comma-separated"
"list of nodes to be upgraded in parallel in this upgrade"
" run invocation. For example: --limit \"compute-0,"
" compute-1, compute-5\".")
)
parser.add_argument('--playbook',
nargs="*",
default=None,
help=_("List of Ansible playbook to use for "
"validations. It could be a playbook path "
"or a list of playbook.")
)
extra_vars_group = parser.add_mutually_exclusive_group(required=False) extra_vars_group = parser.add_mutually_exclusive_group(required=False)
extra_vars_group.add_argument( extra_vars_group.add_argument(
@ -390,6 +270,17 @@ class TripleOValidatorRun(command.Command):
) )
) )
extra_vars_group.add_argument(
'--extra-env-vars',
action='store',
default={},
type=json.loads,
help=_(
"A dictionary as extra environment variables you may need "
"to provide to your Ansible execution example:"
"ANSIBLE_STDOUT_CALLBACK=default")
)
ex_group = parser.add_mutually_exclusive_group(required=True) ex_group = parser.add_mutually_exclusive_group(required=True)
ex_group.add_argument( ex_group.add_argument(
@ -421,37 +312,8 @@ class TripleOValidatorRun(command.Command):
def _run_validator_run(self, parsed_args): def _run_validator_run(self, parsed_args):
LOG = logging.getLogger(__name__ + ".ValidationsRunAnsible") LOG = logging.getLogger(__name__ + ".ValidationsRunAnsible")
playbooks = [] limit = parsed_args.limit
extra_vars_input = {} playbook = parsed_args.playbook
if parsed_args.extra_vars:
extra_vars_input = parsed_args.extra_vars
if parsed_args.extra_vars_file:
extra_vars_input = parsed_args.extra_vars_file
if parsed_args.group:
LOG.debug(_('Getting the validations list by group'))
try:
output = oooutils.parse_all_validations_on_disk(
constants.ANSIBLE_VALIDATION_DIR, parsed_args.group)
for val in output:
playbooks.append(val.get('id') + '.yaml')
except Exception as e:
print(
_("Getting Validations list by group name"
"finished with errors"))
print('Output: {}'.format(e))
else:
for pb in parsed_args.validation_name:
if pb not in oooutils.get_validation_group_name_list():
playbooks.append(pb + '.yaml')
else:
raise exceptions.CommandError(
"Please, use '--group' argument instead of "
"'--validation' to run validation(s) by their name(s)."
)
static_inventory = oooutils.get_tripleo_ansible_inventory( static_inventory = oooutils.get_tripleo_ansible_inventory(
ssh_user='heat-admin', ssh_user='heat-admin',
@ -459,131 +321,45 @@ class TripleOValidatorRun(command.Command):
undercloud_connection='local', undercloud_connection='local',
return_inventory_file_path=True) return_inventory_file_path=True)
failed_val = False v_consts.DEFAULT_VALIDATIONS_BASEDIR = constants.\
DEFAULT_VALIDATIONS_BASEDIR
actions = ValidationActions()
results = actions.run_validations(
playbook=(playbook if playbook else []),
inventory=static_inventory,
limit_hosts=limit,
group=parsed_args.group,
extra_vars=parsed_args.extra_vars,
validations_dir=constants.ANSIBLE_VALIDATION_DIR,
validation_name=parsed_args.validation_name,
extra_env_vars=parsed_args.extra_env_vars,
quiet=parsed_args.quiet)
with oooutils.TempDirs() as tmp: # Build output
with ThreadPoolExecutor(max_workers=parsed_args.workers) as exe: t = PrettyTable(border=True, header=True, padding_width=1)
LOG.debug(_('Running the validations with Ansible')) # Set Field name by getting the result dict keys
tasks_exec = { t.field_names = results[0].keys()
exe.submit( for r in results:
oooutils.run_ansible_playbook, if r.get('Status_by_Host'):
plan=parsed_args.plan, h = []
workdir=tmp, for host in r['Status_by_Host'].split(', '):
playbook=playbook, _name, _status = host.split(',')
playbook_dir=constants.ANSIBLE_VALIDATION_DIR, color = (GREEN if _status == 'PASSED' else RED)
parallel_run=True, _name = '{}{}{}'.format(color, _name, RESET)
inventory=static_inventory, h.append(_name)
output_callback='validation_json', r['Status_by_Host'] = ', '.join(h)
quiet=True, if r.get('status'):
extra_vars=extra_vars_input, status = r.get('status')
gathering_policy='explicit'): playbook color = (CYAN if status in ['starting', 'running']
for playbook in playbooks else GREEN if status == 'PASSED' else RED)
} r['status'] = '{}{}{}'.format(color, status, RESET)
t.add_row(r.values())
results = [] print(t)
for tk, pl in six.iteritems(tasks_exec):
try:
_rc, output = tk.result()
results.append({
'validation': {
'validation_id': pl,
'logfile': None,
'status': 'PASSED',
'output': output
}})
except Exception as e:
failed_val = True
results.append({
'validation': {
'validation_id': pl,
'logfile': None,
'status': 'FAILED',
'output': str(e)
}})
if results:
new_log_files = oooutils.get_new_validations_logs_on_disk()
for i in new_log_files:
val_id = "{}.yaml".format(i.split('_')[1])
for res in results:
if res['validation'].get('validation_id') == val_id:
res['validation']['logfile'] = \
os.path.join(constants.VALIDATIONS_LOG_BASEDIR, i)
t = PrettyTable(border=True, header=True, padding_width=1)
t.field_names = [
"UUID", "Validations", "Status", "Host Group(s)",
"Status by Host", "Unreachable Host(s)", "Duration"]
for validation in results:
r = []
logfile = validation['validation'].get('logfile', None)
if logfile and os.path.exists(logfile):
with open(logfile, 'r') as val:
contents = json.load(val)
for i in contents['plays']:
host = [x for x in i['play'].get('host').split(', ')]
val_id = i['play'].get('validation_id')
time_elapsed = \
i['play']['duration'].get('time_elapsed', None)
r.append(contents['plays'][0]['play'].get('id'))
r.append(val_id)
if validation['validation'].get('status') == "PASSED":
r.append(PASSED_VALIDATION)
else:
r.append(FAILED_VALIDATION)
unreachable_hosts = []
hosts_result = []
for ht in list(contents['stats'].keys()):
if contents['stats'][ht]['unreachable'] != 0:
unreachable_hosts.append(ht)
elif contents['stats'][ht]['failures'] != 0:
hosts_result.append("{}{}{}".format(
RED, ht, RESET))
else:
hosts_result.append("{}{}{}".format(
GREEN, ht, RESET))
r.append(", ".join(host))
r.append(", ".join(hosts_result))
r.append("{}{}{}".format(RED,
", ".join(unreachable_hosts),
RESET))
r.append(time_elapsed)
t.add_row(r)
t.sortby = "UUID"
for field in t.field_names:
if field == "Status":
t.align['Status'] = "l"
else:
t.align[field] = "l"
print(t)
if len(new_log_files) > len(results):
LOG.warn(_('Looks like we have more log files than '
'executed validations'))
for i in new_log_files:
os.rename(
"{}/{}".format(constants.VALIDATIONS_LOG_BASEDIR,
i), "{}/processed_{}".format(
constants.VALIDATIONS_LOG_BASEDIR, i))
LOG.debug(_('Removing static tripleo ansible inventory file')) LOG.debug(_('Removing static tripleo ansible inventory file'))
oooutils.cleanup_tripleo_ansible_inventory_file( oooutils.cleanup_tripleo_ansible_inventory_file(
static_inventory) static_inventory)
if failed_val:
raise exceptions.CommandError(
_('One or more validations have failed!'))
def take_action(self, parsed_args): def take_action(self, parsed_args):
self._run_validator_run(parsed_args) self._run_validator_run(parsed_args)
@ -606,25 +382,22 @@ class TripleOValidatorShowRun(command.Command):
parser.add_argument('--full', parser.add_argument('--full',
action='store_true', action='store_true',
default=True,
help='Show Full Details for the run') help='Show Full Details for the run')
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
logfile_contents = oooutils.parse_all_validations_logs_on_disk( vlogs = ValidationLogs()
uuid_run=parsed_args.uuid) data = vlogs.get_logfile_content_by_uuid(parsed_args.uuid)
if data:
if len(logfile_contents) > 1:
raise exceptions.CommandError(
"Multiple log files found for UUID: %s" % parsed_args.uuid)
if logfile_contents:
if parsed_args.full: if parsed_args.full:
print(oooutils.get_validations_json(logfile_contents[0])) for d in data:
print(json.dumps(d, indent=4, sort_keys=True))
else: else:
for data in logfile_contents: for d in data:
for tasks in data['validation_output']: for p in d['plays']:
print(oooutils.get_validations_json(tasks)) print(json.dumps(p['tasks'], indent=4, sort_keys=True))
else: else:
raise exceptions.CommandError( raise exceptions.CommandError(
"Could not find the log file linked to this UUID: %s" % "Could not find the log file linked to this UUID: %s" %
@ -645,41 +418,5 @@ class TripleOValidatorShowHistory(command.Lister):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
logfile_contents = oooutils.parse_all_validations_logs_on_disk( actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
validation_id=parsed_args.validation) return actions.show_history(parsed_args.validation)
if not logfile_contents:
msg = "No History Found"
if parsed_args.validation:
raise exceptions.CommandError(
"{} for {}.".format(
msg, parsed_args.validation))
else:
raise exceptions.CommandError(
"{}.".format(msg, parsed_args.validation))
return_values = []
column_name = ('UUID', 'Validations',
'Status', 'Execution at',
'Duration')
for run in logfile_contents:
status = PASSED_VALIDATION
if 'plays' in run and run.get('plays'):
date_time = \
run['plays'][0]['play']['duration'].get('start').split('T')
time_elapsed = \
run['plays'][0]['play']['duration'].get('time_elapsed')
date_start = date_time[0]
time_start = date_time[1].split('Z')[0]
for k, v in six.iteritems(run['stats']):
if v.get('failures') != 0:
status = FAILED_VALIDATION
return_values.append(
(run['plays'][0]['play'].get('id'),
run['plays'][0]['play'].get('validation_id'), status,
"{} {}".format(date_start, time_start), time_elapsed))
return (column_name, return_values)