Use new CLI for functionnal tests and remove validation.py script
Depends-On: https://review.opendev.org/c/openstack/validations-libs/+/782574 Change-Id: Ib2da7a386080094f8733f831cb7c8bfa07c12222
This commit is contained in:
parent
5e9cb46601
commit
10361e1e8b
@ -4,27 +4,27 @@ zuul_work_virtualenv: "{{ ansible_user_dir }}/.venv"
|
|||||||
ansible_dir: "{{ zuul_work_virtualenv }}/share/ansible/"
|
ansible_dir: "{{ zuul_work_virtualenv }}/share/ansible/"
|
||||||
validation_dir: "{{ zuul_work_virtualenv }}/share/ansible/validation-playbooks"
|
validation_dir: "{{ zuul_work_virtualenv }}/share/ansible/validation-playbooks"
|
||||||
vf_log_dir: "/var/log/validations"
|
vf_log_dir: "/var/log/validations"
|
||||||
val_exec: "source {{ zuul_work_virtualenv }}/bin/activate; validation.py"
|
val_exec: "source {{ zuul_work_virtualenv }}/bin/activate; validation"
|
||||||
command:
|
command:
|
||||||
- output: "{{ log_dir }}/run.log"
|
- output: "{{ log_dir }}/run.log"
|
||||||
|
action: "run"
|
||||||
command: >-
|
command: >-
|
||||||
{{ val_exec }} run --validation check-ftype,512e
|
{{ val_exec }} run --validation check-ftype,512e
|
||||||
--validation-dir {{ validation_dir }}
|
--validation-dir {{ validation_dir }}
|
||||||
--ansible-base-dir {{ ansible_dir }}
|
--ansible-base-dir {{ ansible_dir }}
|
||||||
--output-log {{ log_dir }}/run.log
|
--output-log {{ log_dir }}/run.log
|
||||||
- output: "{{ log_dir }}/run-group.log"
|
- output: "{{ log_dir }}/run-group.log"
|
||||||
|
action: "run"
|
||||||
command: >-
|
command: >-
|
||||||
{{ val_exec }} run --group prep --validation-dir {{ validation_dir }}
|
{{ val_exec }} run --group prep --validation-dir {{ validation_dir }}
|
||||||
--ansible-base-dir {{ ansible_dir }}
|
--ansible-base-dir {{ ansible_dir }}
|
||||||
--output-log {{ log_dir }}/run-group.log
|
--output-log {{ log_dir }}/run-group.log
|
||||||
--extra-vars minimal_ram_gb=7
|
--extra-vars minimal_ram_gb=7
|
||||||
- output: "{{ log_dir }}/list.log"
|
- output: "{{ log_dir }}/list.log"
|
||||||
|
action: "list"
|
||||||
command: >-
|
command: >-
|
||||||
{{ val_exec }} list --validation-dir {{ validation_dir }}
|
{{ val_exec }} list --validation-dir {{ validation_dir }} -f json > {{ log_dir }}/list.log 2>&1
|
||||||
--ansible-base-dir {{ ansible_dir }}
|
|
||||||
--output-log {{ log_dir }}/list.log
|
|
||||||
- output: "{{ log_dir }}/show.log"
|
- output: "{{ log_dir }}/show.log"
|
||||||
|
action: "show"
|
||||||
command: >-
|
command: >-
|
||||||
{{ val_exec }} show --validation-dir {{ validation_dir }}
|
{{ val_exec }} show --validation-dir {{ validation_dir }} check-ram -f json > {{ log_dir }}/show.log 2>&1
|
||||||
--ansible-base-dir {{ ansible_dir }}
|
|
||||||
--output-log {{ log_dir }}/show.log
|
|
||||||
|
@ -26,5 +26,5 @@
|
|||||||
name: ansible
|
name: ansible
|
||||||
virtualenv: "{{ zuul_work_virtualenv }}"
|
virtualenv: "{{ zuul_work_virtualenv }}"
|
||||||
|
|
||||||
- include: validations.yaml validation_command="{{ item.command }}" val_output="{{ item.output }}"
|
- include: validations.yaml validation_command="{{ item.command }}" val_output="{{ item.output }}" action="{{ item.action }}"
|
||||||
loop: "{{command}}"
|
loop: "{{command}}"
|
||||||
|
@ -4,23 +4,30 @@
|
|||||||
cmd: "{{ validation_command }}"
|
cmd: "{{ validation_command }}"
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
|
|
||||||
- name: Get run results
|
- name: set fact for Validation action
|
||||||
register: result
|
set_fact: v_action="{{ action }}"
|
||||||
shell:
|
|
||||||
cmd: "cat {{ val_output }}"
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Get json data
|
- name: Get Run results
|
||||||
set_fact:
|
block:
|
||||||
jsondata: "{{ result.stdout | from_json }}"
|
- name: Get run results
|
||||||
|
register: result
|
||||||
|
shell:
|
||||||
|
cmd: "cat {{ val_output }}"
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
- name: Get Validations Status
|
- name: Get json data
|
||||||
set_fact:
|
set_fact:
|
||||||
status: "{{ jsondata | json_query(jsonres) }}"
|
jsondata: "{{ result.stdout | from_json }}"
|
||||||
vars:
|
|
||||||
jsonres: 'results[*].Status'
|
|
||||||
|
|
||||||
- fail:
|
- name: Get Validations Status
|
||||||
msg: "Validation failed: some of the validations has failed."
|
set_fact:
|
||||||
when: item != "PASSED"
|
status: "{{ jsondata | json_query(jsonres) }}"
|
||||||
loop: "{{ status }}"
|
vars:
|
||||||
|
jsonres: 'results[*].Status'
|
||||||
|
|
||||||
|
- fail:
|
||||||
|
msg: "Validation failed: some of the validations has failed."
|
||||||
|
when:
|
||||||
|
- item != "PASSED"
|
||||||
|
loop: "{{ status }}"
|
||||||
|
when: v_action == "run"
|
||||||
|
@ -26,9 +26,6 @@ classifier =
|
|||||||
packages =
|
packages =
|
||||||
validations_common
|
validations_common
|
||||||
|
|
||||||
scripts =
|
|
||||||
validations_common/validation.py
|
|
||||||
|
|
||||||
data_files =
|
data_files =
|
||||||
share/ansible/roles = roles/*
|
share/ansible/roles = roles/*
|
||||||
share/ansible/roles = validations_common/roles/*
|
share/ansible/roles = validations_common/roles/*
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
test_validation
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Tests for `validation` sub module.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from validations_common import validation
|
|
||||||
from validations_common.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidationModule(base.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.module = mock.MagicMock()
|
|
||||||
|
|
||||||
super(TestValidationModule, self).setUp()
|
|
||||||
|
|
||||||
def test_module_init(self):
|
|
||||||
"""Check for presence of required module attributes.
|
|
||||||
Including the color constants needed for output formatting.
|
|
||||||
"""
|
|
||||||
required_attributes = [
|
|
||||||
'DESCRIPTION',
|
|
||||||
'EPILOG',
|
|
||||||
'RED',
|
|
||||||
'GREEN',
|
|
||||||
'CYAN',
|
|
||||||
'RESET'
|
|
||||||
]
|
|
||||||
|
|
||||||
module_attributes = set(dir(validation))
|
|
||||||
|
|
||||||
self.assertTrue(set(required_attributes).issubset(module_attributes))
|
|
||||||
|
|
||||||
|
|
||||||
class TestCommaListGroupAction(base.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCommaListGroupAction, self).setUp()
|
|
||||||
|
|
||||||
@mock.patch('validations_common.validation.setattr')
|
|
||||||
def test_comma_list_group_action(self, mock_setattr):
|
|
||||||
"""As there is not much to test in this class
|
|
||||||
the assertions are made on attributes and inheritance.
|
|
||||||
"""
|
|
||||||
action = validation._CommaListGroupAction(
|
|
||||||
dest='foo',
|
|
||||||
option_strings='')
|
|
||||||
|
|
||||||
action('foo', 'bar', 'fizz,buzz')
|
|
||||||
|
|
||||||
self.assertEqual(type(action).__mro__[1], argparse.Action)
|
|
||||||
self.assertEqual(action.dest, 'foo')
|
|
||||||
self.assertEqual(action.option_strings, '')
|
|
||||||
mock_setattr.assert_called_once_with('bar', 'foo', ['fizz', 'buzz'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestCommaListAction(base.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCommaListAction, self).setUp()
|
|
||||||
|
|
||||||
@mock.patch('validations_common.validation.setattr')
|
|
||||||
def test_comma_list_action(self, mock_setattr):
|
|
||||||
"""As there is not much to test in this class
|
|
||||||
the assertions are made on attributes and inheritance.
|
|
||||||
"""
|
|
||||||
action = validation._CommaListAction(
|
|
||||||
dest='foo',
|
|
||||||
option_strings='')
|
|
||||||
|
|
||||||
action('foo', 'bar', 'fizz,buzz')
|
|
||||||
|
|
||||||
self.assertEqual(type(action).__mro__[1], argparse.Action)
|
|
||||||
self.assertEqual(action.dest, 'foo')
|
|
||||||
self.assertEqual(action.option_strings, '')
|
|
||||||
mock_setattr.assert_called_once_with('bar', 'foo', ['fizz', 'buzz'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidation(base.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestValidation, self).setUp()
|
|
||||||
|
|
||||||
def test_validation_init(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_parser(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_print_dict_table(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_print_tuple_table(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_write_output(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_take_action_run(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_take_action_list(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_validation_take_action_show(self):
|
|
||||||
pass
|
|
@ -1,249 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Copyright 2020 Red Hat, Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
"""
|
|
||||||
Uses `validations_libs`_.
|
|
||||||
|
|
||||||
.. _validations_libs: https://opendev.org/openstack/validations-libs
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from prettytable import PrettyTable
|
|
||||||
|
|
||||||
from validations_libs.validation_actions import ValidationActions
|
|
||||||
from validations_libs import constants
|
|
||||||
|
|
||||||
DESCRIPTION = "Run, show or list Validations."
|
|
||||||
EPILOG = "Example: ./validation run --validation check-ftype,512e"
|
|
||||||
# PrettyTable
|
|
||||||
RED = "\033[1;31m"
|
|
||||||
GREEN = "\033[0;32m"
|
|
||||||
CYAN = "\033[36m"
|
|
||||||
RESET = "\033[0;0m"
|
|
||||||
|
|
||||||
|
|
||||||
class _CommaListGroupAction(argparse.Action):
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
setattr(namespace, self.dest, values.split(','))
|
|
||||||
|
|
||||||
|
|
||||||
class _CommaListAction(argparse.Action):
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
setattr(namespace, self.dest, values.split(','))
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationsFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter):
|
|
||||||
"""
|
|
||||||
Composite class inheriting from both ArgumentDefaultsHelpFormatter
|
|
||||||
and RawTextHelpFormatter.
|
|
||||||
Thus allowing for both more precise help output management
|
|
||||||
and automatic printing of default arg values.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Validation(argparse.ArgumentParser):
|
|
||||||
"""Validation client implementation class"""
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__ + ".Validation")
|
|
||||||
|
|
||||||
def __init__(self, description=DESCRIPTION, epilog=EPILOG):
|
|
||||||
"""Init validation paser"""
|
|
||||||
super(Validation, self).__init__(
|
|
||||||
description=DESCRIPTION,
|
|
||||||
epilog=EPILOG,
|
|
||||||
formatter_class=ValidationsFormatter)
|
|
||||||
|
|
||||||
def parser(self, parser):
|
|
||||||
"""Argument parser for validation"""
|
|
||||||
parser.add_argument('action',
|
|
||||||
choices=['run', 'list', 'show'],
|
|
||||||
help=(
|
|
||||||
'Validation Action: \n'
|
|
||||||
' run:\n launches validations specified by '
|
|
||||||
'the --group or --validation args. \n'
|
|
||||||
' list:\n prints list of available validations,'
|
|
||||||
'including their groups. \n'
|
|
||||||
' show:\n prints list of validations executions and related info.'
|
|
||||||
))
|
|
||||||
parser.add_argument('--inventory', '-i', type=str,
|
|
||||||
default="localhost",
|
|
||||||
help=(
|
|
||||||
"Either a path of the Ansible inventory file, \n"
|
|
||||||
"or a comma-separated list of hosts. \n"
|
|
||||||
))
|
|
||||||
parser.add_argument('--extra-vars', action='store',
|
|
||||||
nargs='+',
|
|
||||||
help="Extra ansible variables")
|
|
||||||
parser.add_argument('--validation', '-v',
|
|
||||||
metavar='<validation_id>[,<validation_id>,...]',
|
|
||||||
dest="validation_name",
|
|
||||||
action=_CommaListAction,
|
|
||||||
default=[],
|
|
||||||
help=("Run specific validations, \n"
|
|
||||||
"if more than one validation is required \n"
|
|
||||||
"separate the names with commas: \n"
|
|
||||||
"--validation check-ftype,512e | \n"
|
|
||||||
"--validation 512e \n"))
|
|
||||||
parser.add_argument('--group', '-g',
|
|
||||||
metavar='<group>[,<group>,...]',
|
|
||||||
action=_CommaListGroupAction,
|
|
||||||
default=[],
|
|
||||||
help=("Run specific group of validations, \n"
|
|
||||||
"if more than one group is required \n"
|
|
||||||
"separate the group names with commas: \n"
|
|
||||||
"--group pre-upgrade,prep | \n"
|
|
||||||
"--group openshift-on-openstack \n"))
|
|
||||||
parser.add_argument('--quiet', action='store_true',
|
|
||||||
help=("Run Ansible in silent mode. \n"))
|
|
||||||
parser.add_argument('--validation-dir', dest='validation_dir',
|
|
||||||
default=constants.ANSIBLE_VALIDATION_DIR,
|
|
||||||
help=("Path where the validation playbooks "
|
|
||||||
"are located. \n"))
|
|
||||||
parser.add_argument('--ansible-base-dir', dest='ansible_base_dir',
|
|
||||||
default=constants.DEFAULT_VALIDATIONS_BASEDIR,
|
|
||||||
help=("Path where the ansible roles, library \n"
|
|
||||||
"and plugins are located. \n"))
|
|
||||||
parser.add_argument('--output-log', dest='output_log',
|
|
||||||
default=None,
|
|
||||||
help=("Path where the run result will be stored. \n"))
|
|
||||||
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
def _print_dict_table(self, data):
|
|
||||||
"""Print table from python dict with PrettyTable"""
|
|
||||||
t = PrettyTable(border=True, header=True, padding_width=1)
|
|
||||||
# Set Field name by getting the result dict keys
|
|
||||||
try:
|
|
||||||
t.field_names = data[0].keys()
|
|
||||||
t.align = 'l'
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError()
|
|
||||||
for r in data:
|
|
||||||
if r.get('Status_by_Host'):
|
|
||||||
h = []
|
|
||||||
for host in r['Status_by_Host'].split(', '):
|
|
||||||
_name, _status = host.split(',')
|
|
||||||
color = (GREEN if _status == 'PASSED' else RED)
|
|
||||||
_name = '{}{}{}'.format(color, _name, RESET)
|
|
||||||
h.append(_name)
|
|
||||||
r['Status_by_Host'] = ', '.join(h)
|
|
||||||
if r.get('Status'):
|
|
||||||
status = r.get('Status')
|
|
||||||
color = (CYAN if status in ['starting', 'running']
|
|
||||||
else GREEN if status == 'PASSED' else RED)
|
|
||||||
r['Status'] = '{}{}{}'.format(color, status, RESET)
|
|
||||||
t.add_row(r.values())
|
|
||||||
print(t)
|
|
||||||
|
|
||||||
def _print_tuple_table(self, data, status_col=None):
|
|
||||||
"""Print table from python Tuple with PrettyTable"""
|
|
||||||
if isinstance(data, tuple):
|
|
||||||
t = PrettyTable(border=True, header=True, padding_width=1)
|
|
||||||
try:
|
|
||||||
t.field_names = data[0]
|
|
||||||
t.align = 'l'
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError()
|
|
||||||
for r in data[1]:
|
|
||||||
if status_col:
|
|
||||||
_result = list(r)
|
|
||||||
try:
|
|
||||||
_status = _result[status_col]
|
|
||||||
color = (GREEN if _status == 'PASSED' else RED)
|
|
||||||
_result[status_col] = '{}{}{}'.format(color,
|
|
||||||
_status,
|
|
||||||
RESET)
|
|
||||||
except ValueError:
|
|
||||||
logging.warning('No status found.')
|
|
||||||
t.add_row(_result)
|
|
||||||
else:
|
|
||||||
t.add_row(r)
|
|
||||||
print(t)
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
|
||||||
(
|
|
||||||
"data must be of 'tuple' type. "
|
|
||||||
"Instead {} was provided."
|
|
||||||
).format(type(data))
|
|
||||||
)
|
|
||||||
|
|
||||||
def _write_output(self, output_log, results):
|
|
||||||
"""Write output log file as Json format"""
|
|
||||||
with open(output_log, 'w') as output:
|
|
||||||
output.write(json.dumps({'results': results}, indent=4,
|
|
||||||
sort_keys=True))
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
"""Take validation action"""
|
|
||||||
# Get parameters:
|
|
||||||
action = parsed_args.action
|
|
||||||
inventory = parsed_args.inventory
|
|
||||||
group = parsed_args.group
|
|
||||||
validation_name = parsed_args.validation_name
|
|
||||||
quiet = parsed_args.quiet
|
|
||||||
validation_dir = parsed_args.validation_dir
|
|
||||||
ansible_base_dir = parsed_args.ansible_base_dir
|
|
||||||
extra_vars = parsed_args.extra_vars
|
|
||||||
if extra_vars:
|
|
||||||
try:
|
|
||||||
extra_vars = dict(e.split("=") for e in parsed_args.extra_vars)
|
|
||||||
except ValueError as error:
|
|
||||||
msg = "extra vars option should be formed as: KEY=VALUE."
|
|
||||||
raise RuntimeError(msg) from error
|
|
||||||
v_actions = ValidationActions(validation_path=validation_dir,
|
|
||||||
group=group)
|
|
||||||
if 'run' in action:
|
|
||||||
try:
|
|
||||||
results = v_actions.run_validations(
|
|
||||||
inventory=inventory,
|
|
||||||
group=group,
|
|
||||||
validation_name=validation_name,
|
|
||||||
base_dir=ansible_base_dir,
|
|
||||||
extra_vars=extra_vars,
|
|
||||||
quiet=quiet)
|
|
||||||
except RuntimeError as e:
|
|
||||||
sys.exit(e)
|
|
||||||
if results:
|
|
||||||
if parsed_args.output_log:
|
|
||||||
self._write_output(parsed_args.output_log, results)
|
|
||||||
else:
|
|
||||||
self._print_dict_table(results)
|
|
||||||
elif 'list' in action:
|
|
||||||
results = v_actions.list_validations()
|
|
||||||
if results:
|
|
||||||
if parsed_args.output_log:
|
|
||||||
self._write_output(parsed_args.output_log, results)
|
|
||||||
else:
|
|
||||||
self._print_tuple_table(results)
|
|
||||||
elif 'show' in action:
|
|
||||||
results = v_actions.show_history(validation_name)
|
|
||||||
if results:
|
|
||||||
if parsed_args.output_log:
|
|
||||||
self._write_output(parsed_args.output_log, results)
|
|
||||||
else:
|
|
||||||
self._print_tuple_table(data=results, status_col=2)
|
|
||||||
else:
|
|
||||||
msg = "Unknown Action: {}".format(action)
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
validation = Validation()
|
|
||||||
args = validation.parser(validation)
|
|
||||||
validation.take_action(args)
|
|
Loading…
Reference in New Issue
Block a user