Revert the move of tripleo_validator in tripleo-validation

This revert is done due to several reasons:
* The move of the CLI wont be backported in the stable branches so
  we will have to maintain one CLI in tripleo-validations for Master
  and tripleoclient for the older branches
* We plan to form tripleo-validations as an Ansible collection so
  having the dedicated tripleo validations CLI outside now make sense
* We are creating a core CLI in the framework itself in order to converge
  all the logics in one uniq place. The tripleoclient implementation will
  be only caller on top of this, without almost no logic in the code, just
  a specific tripleo layer

Change-Id: Iac8601399a835e55b53c4820e280e255f624ceec
This commit is contained in:
matbu 2021-03-24 15:58:49 +01:00
parent 4678d5053c
commit eb6d660efc
3 changed files with 780 additions and 0 deletions

View File

@ -99,6 +99,13 @@ openstack.tripleoclient.v2 =
undercloud_minion_install = tripleoclient.v1.undercloud_minion:InstallUndercloudMinion
undercloud_minion_upgrade = tripleoclient.v1.undercloud_minion:UpgradeUndercloudMinion
undercloud_backup = tripleoclient.v1.undercloud_backup:BackupUndercloud
tripleo_validator_group_info = tripleoclient.v1.tripleo_validator:TripleOValidatorGroupInfo
tripleo_validator_list = tripleoclient.v1.tripleo_validator:TripleOValidatorList
tripleo_validator_run = tripleoclient.v1.tripleo_validator:TripleOValidatorRun
tripleo_validator_show = tripleoclient.v1.tripleo_validator:TripleOValidatorShow
tripleo_validator_show_history = tripleoclient.v1.tripleo_validator:TripleOValidatorShowHistory
tripleo_validator_show_parameter = tripleoclient.v1.tripleo_validator:TripleOValidatorShowParameter
tripleo_validator_show_run = tripleoclient.v1.tripleo_validator:TripleOValidatorShowRun
oslo.config.opts =
undercloud_config = tripleoclient.config.undercloud:list_opts

View File

@ -0,0 +1,279 @@
# Copyright 2018 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.
#
import mock
from osc_lib.tests import utils
from tripleoclient.v1 import tripleo_validator
VALIDATIONS_LIST = [{
'description': 'My Validation One Description',
'groups': ['prep', 'pre-deployment'],
'id': 'my_val1',
'name': 'My Validition One Name',
'parameters': {}
}, {
'description': 'My Validation Two Description',
'groups': ['prep', 'pre-introspection'],
'id': 'my_val2',
'name': 'My Validition Two Name',
'parameters': {'min_value': 8}
}]
GROUPS_LIST = [
('group1', 'Group1 description'),
('group2', 'Group2 description'),
('group3', 'Group3 description'),
]
VALIDATIONS_LOGS_CONTENTS_LIST = [{
'plays': [{
'play': {
'duration': {
'end': '2019-11-25T13:40:17.538611Z',
'start': '2019-11-25T13:40:14.404623Z',
'time_elapsed': '0:00:03.753'
},
'host': 'undercloud',
'id': '008886df-d297-1eaa-2a74-000000000008',
'validation_id': '512e',
'validation_path':
'/usr/share/ansible/validation-playbooks'
},
'tasks': [
{
'hosts': {
'undercloud': {
'_ansible_no_log': False,
'action': 'command',
'changed': False,
'cmd': [u'ls', '/sys/class/block/'],
'delta': '0:00:00.018913',
'end': '2019-11-25 13:40:17.120368',
'invocation': {
'module_args': {
'_raw_params': 'ls /sys/class/block/',
'_uses_shell': False,
'argv': None,
'chdir': None,
'creates': None,
'executable': None,
'removes': None,
'stdin': None,
'stdin_add_newline': True,
'strip_empty_ends': True,
'warn': True
}
},
'rc': 0,
'start': '2019-11-25 13:40:17.101455',
'stderr': '',
'stderr_lines': [],
'stdout': 'vda',
'stdout_lines': [u'vda']
}
},
'task': {
'duration': {
'end': '2019-11-25T13:40:17.336687Z',
'start': '2019-11-25T13:40:14.529880Z'
},
'id':
'008886df-d297-1eaa-2a74-00000000000d',
'name':
'advanced-format-512e-support : List the available drives'
}
},
{
'hosts': {
'undercloud': {
'action':
'advanced_format',
'changed': False,
'msg':
'All items completed',
'results': [{
'_ansible_item_label': 'vda',
'_ansible_no_log': False,
'ansible_loop_var': 'item',
'changed': False,
'item': 'vda',
'skip_reason': 'Conditional result was False',
'skipped': True
}],
'skipped': True
}
},
'task': {
'duration': {
'end': '2019-11-25T13:40:17.538611Z',
'start': '2019-11-25T13:40:17.341704Z'
},
'id': '008886df-d297-1eaa-2a74-00000000000e',
'name':
'advanced-format-512e-support: Detect the drive'
}
}
]
}],
'stats': {
'undercloud': {
'changed': 0,
'failures': 0,
'ignored': 0,
'ok': 1,
'rescued': 0,
'skipped': 1,
'unreachable': 0
}
},
'validation_output': []
}]
class TestValidatorGroupInfo(utils.TestCommand):
def setUp(self):
super(TestValidatorGroupInfo, self).setUp()
# Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorGroupInfo(self.app, None)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'group_information', return_value=GROUPS_LIST)
def test_show_group_info(self, mock_validations):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
class TestValidatorList(utils.TestCommand):
def setUp(self):
super(TestValidatorList, self).setUp()
# Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorList(self.app, None)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'list_validations',
return_value=VALIDATIONS_LIST)
def test_validation_list_noargs(self, mock_validations):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
class TestValidatorShow(utils.TestCommand):
def setUp(self):
super(TestValidatorShow, self).setUp()
# Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorShow(self.app, None)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_validations',
return_value=VALIDATIONS_LIST[0])
def test_validation_show(self, mock_validations):
arglist = ['my_val1']
verifylist = [('validation_id', 'my_val1')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
class TestValidatorShowParameter(utils.TestCommand):
def setUp(self):
super(TestValidatorShowParameter, self).setUp()
# Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorShowParameter(self.app,
None)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_validations_parameters',
return_value=VALIDATIONS_LIST[1])
def test_validation_show_parameter(self, mock_validations):
arglist = ['--validation', 'my_val2']
verifylist = [('validation_name', ['my_val2'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
class TestValidatorShowRun(utils.TestCommand):
def setUp(self):
super(TestValidatorShowRun, self).setUp()
# Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorShowRun(self.app,
None)
@mock.patch('validations_libs.validation_actions.ValidationLogs.'
'get_logfile_content_by_uuid',
return_value=VALIDATIONS_LOGS_CONTENTS_LIST)
def test_validation_show_run(self, mock_validations):
arglist = ['008886df-d297-1eaa-2a74-000000000008']
verifylist = [('uuid', '008886df-d297-1eaa-2a74-000000000008')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
class TestValidatorShowHistory(utils.TestCommand):
def setUp(self):
super(TestValidatorShowHistory, self).setUp()
# Get the command object to test
self.cmd = tripleo_validator.TripleOValidatorShowHistory(self.app,
None)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history',
return_value=VALIDATIONS_LOGS_CONTENTS_LIST)
def test_validation_show_history(self, mock_validations):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
@mock.patch('validations_libs.validation_actions.ValidationActions.'
'show_history',
return_value=VALIDATIONS_LOGS_CONTENTS_LIST)
def test_validation_show_history_for_a_validation(self, mock_validations):
arglist = [
'--validation',
'512e'
]
verifylist = [('validation', '512e')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)

View File

@ -0,0 +1,494 @@
# Copyright 2019 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.
#
import argparse
import json
import logging
import yaml
from openstack import exceptions as os_exceptions
from osc_lib import exceptions
from osc_lib.i18n import _
from prettytable import PrettyTable
from osc_lib.command import command
from tripleoclient import utils as oooutils
from tripleoclient.workflows import deployment
from tripleoclient import constants
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")
RED = "\033[1;31m"
GREEN = "\033[0;32m"
CYAN = "\033[36m"
YELLOW = "\033[0;33m"
RESET = "\033[0;0m"
FAILED_VALIDATION = "{}FAILED{}".format(RED, RESET)
PASSED_VALIDATION = "{}PASSED{}".format(GREEN, RESET)
GROUP_FILE = constants.VALIDATION_GROUPS_INFO
NO_VALIDATION_STATE = ['DEPLOY_FAILED', 'DEPLOYING']
class _CommaListGroupAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
opts = v_utils.get_validation_group_name_list(GROUP_FILE)
for value in values.split(','):
if value not in opts:
message = ("Invalid choice: {value} (choose from {choice})"
.format(value=value,
choice=opts))
raise argparse.ArgumentError(self, message)
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 TripleOValidatorGroupInfo(command.Lister):
"""Display Information about Validation Groups"""
auth_required = False
def get_parser(self, prog_name):
parser = super(TripleOValidatorGroupInfo, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
return actions.group_information(GROUP_FILE)
class TripleOValidatorShow(command.ShowOne):
"""Display detailed information about a Validation"""
auth_required = False
def get_parser(self, prog_name):
parser = super(TripleOValidatorShow, self).get_parser(prog_name)
parser.add_argument('validation_id',
metavar="<validation>",
type=str,
help='Validation ID')
return parser
def take_action(self, parsed_args):
LOG.debug(_('Show validation result'))
actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
try:
data = actions.show_validations(parsed_args.validation_id)
except Exception as e:
raise exceptions.CommandError(e)
if data:
return data.keys(), data.values()
class TripleOValidatorShowParameter(command.Command):
"""Display Validations Parameters"""
auth_required = False
def get_parser(self, prog_name):
parser = argparse.ArgumentParser(
description=self.get_description(),
prog=prog_name,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=True
)
ex_group = parser.add_mutually_exclusive_group(required=False)
ex_group.add_argument(
'--validation',
metavar='<validation_id>[,<validation_id>,...]',
dest='validation_name',
action=_CommaListAction,
default=[],
help=_("List specific validations, "
"if more than one validation is required "
"separate the names with commas: "
"--validation check-ftype,512e | "
"--validation 512e")
)
ex_group.add_argument(
'--group',
metavar='<group_id>[,<group_id>,...]',
action=_CommaListGroupAction,
default=[],
help=_("List specific group validations, "
"if more than one group is required "
"separate the group names with commas: "
"pre-upgrade,prep | "
"openshift-on-openstack")
)
parser.add_argument(
'--download',
action='store',
default=None,
help=_("Create a json or a yaml file "
"containing all the variables "
"available for the validations: "
"/tmp/myvars")
)
parser.add_argument(
'-f', '--format',
action='store',
metavar='<format>',
default='json',
choices=['json', 'yaml'],
help=_("Print representation of the validation. "
"The choices of the output format is json,yaml. ")
)
return parser
def take_action(self, 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):
"""List the available validations"""
auth_required = False
def get_parser(self, prog_name):
parser = super(TripleOValidatorList, self).get_parser(prog_name)
parser.add_argument(
'--group',
metavar='<group>[,<group>,...]',
action=_CommaListGroupAction,
default=[],
help=_("List specific group validations, "
"if more than one group is required "
"separate the group names with commas: "
"--group pre-upgrade,prep | "
"--group openshift-on-openstack")
)
return parser
def take_action(self, parsed_args):
LOG.debug(_('Launch listing the validations'))
try:
v_consts.DEFAULT_VALIDATIONS_BASEDIR = constants.\
DEFAULT_VALIDATIONS_BASEDIR
actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR,
parsed_args.group)
return actions.list_validations()
except Exception as e:
raise RuntimeError(_("Validations listing finished with errors\n"
"Output: {}").format(e))
class TripleOValidatorRun(command.Command):
"""Run the available validations"""
auth_required = False
def get_parser(self, prog_name):
parser = argparse.ArgumentParser(
description=self.get_description(),
prog=prog_name,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False
)
parser.add_argument(
'--plan', '--stack',
dest='plan',
default=None,
help=_("Execute the validations using a custom plan name")
)
parser.add_argument(
'--ssh-user',
dest='ssh_user',
default='heat-admin',
help=_("Ssh User name for the Overcloud ssh connection.")
)
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\".")
)
extra_vars_group = parser.add_mutually_exclusive_group(required=False)
extra_vars_group.add_argument(
'--extra-vars',
action='store',
default={},
type=json.loads,
help=_(
"Add a dictionary as extra variable to a validation: "
"--extra-vars '{\"min_undercloud_ram_gb\": 24}'")
)
extra_vars_group.add_argument(
'--extra-vars-file',
action='store',
default='',
help=_(
"Add a JSON/YAML file containing extra variable "
"to a validation: "
"--extra-vars-file /home/stack/vars.[json|yaml] "
"If using Mistral, only a valid JSON file will be "
"supported."
)
)
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")
)
extra_vars_group.add_argument(
'--static-inventory',
action='store',
default='',
help=_(
"Provide your own static inventory file. You can generate "
"such an inventory calling tripleo-ansible-inventory command. "
"Especially useful when heat service isn't available."
)
)
ex_group = parser.add_mutually_exclusive_group(required=True)
ex_group.add_argument(
'--validation',
metavar='<validation_id>[,<validation_id>,...]',
dest="validation_name",
action=_CommaListAction,
default=[],
help=_("Run specific validations, "
"if more than one validation is required "
"separate the names with commas: "
"--validation check-ftype,512e | "
"--validation 512e")
)
ex_group.add_argument(
'--group',
metavar='<group>[,<group>,...]',
action=_CommaListGroupAction,
default=[],
help=_("Run specific group validations, "
"if more than one group is required "
"separate the group names with commas: "
"--group pre-upgrade,prep | "
"--group openshift-on-openstack")
)
return parser
def _run_validator_run(self, parsed_args):
LOG = logging.getLogger(__name__ + ".ValidationsRunAnsible")
plan = parsed_args.plan
# Try to perform OpenStack authentication, if no authentication
# and static inventory provided continue, else raise error.
try:
clients = self.app.client_manager
clients._auth_required = True
clients.setup_auth()
except os_exceptions.ConfigException:
msg = "Running Validations without authentication."
LOG.warning("{}{}{}".format(YELLOW, msg, RESET))
if not parsed_args.static_inventory:
raise exceptions.CommandError(
_("No static inventory provided, please provide a valid "
"inventory or use authentication."))
else:
if plan:
status = deployment.get_deployment_status(clients, plan)
if not status or status in NO_VALIDATION_STATE:
raise exceptions.CommandError(
_("The plan and the stack '{}' doesn't exist OR are "
"in 'failed' or 'deploying' state. "
"Please use a valid plan".format(plan)))
else:
msg = "Running Validations without Overcloud settings."
LOG.warning("{}{}{}".format(YELLOW, msg, RESET))
limit = parsed_args.limit
extra_vars = parsed_args.extra_vars
if parsed_args.extra_vars_file:
try:
with open(parsed_args.extra_vars_file, 'r') as env_file:
extra_vars.update(yaml.safe_load(env_file.read()))
except yaml.YAMLError as e:
error_msg = (
"The request body must be properly formatted YAML/JSON. "
"Details: %s." % e)
raise exceptions.CommandError(error_msg)
# We don't check if the file exists in order to support
# passing a string such as "localhost,", like we can do with
# the "-i" option of ansible-playbook.
if parsed_args.static_inventory:
static_inventory = parsed_args.static_inventory
else:
static_inventory = oooutils.get_tripleo_ansible_inventory(
ssh_user=parsed_args.ssh_user,
stack=parsed_args.plan,
undercloud_connection='local',
return_inventory_file_path=True)
v_consts.DEFAULT_VALIDATIONS_BASEDIR = constants.\
DEFAULT_VALIDATIONS_BASEDIR
actions = ValidationActions()
try:
results = actions.run_validations(
inventory=static_inventory,
limit_hosts=limit,
group=parsed_args.group,
extra_vars=extra_vars,
validations_dir=constants.ANSIBLE_VALIDATION_DIR,
validation_name=parsed_args.validation_name,
extra_env_vars=parsed_args.extra_env_vars,
quiet=True)
except RuntimeError as e:
raise exceptions.CommandError(e)
if results:
# Build output
t = PrettyTable(border=True, header=True, padding_width=1)
# Set Field name by getting the result dict keys
t.field_names = results[0].keys()
for r in results:
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)
else:
msg = "No Validation has been run, please check your parameters."
LOG.info(_(msg))
if not parsed_args.static_inventory:
LOG.debug(_('Removing static tripleo ansible inventory file'))
oooutils.cleanup_tripleo_ansible_inventory_file(
static_inventory)
def take_action(self, parsed_args):
self._run_validator_run(parsed_args)
class TripleOValidatorShowRun(command.Command):
"""Display details about a Validation execution"""
auth_required = False
def get_parser(self, prog_name):
parser = argparse.ArgumentParser(
description=self.get_description(),
prog=prog_name,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False
)
parser.add_argument('uuid',
metavar="<uuid>",
type=str,
help='Validation UUID Run')
parser.add_argument('--full',
action='store_true',
help='Show Full Details for the run')
return parser
def take_action(self, parsed_args):
vlogs = ValidationLogs()
data = vlogs.get_logfile_content_by_uuid(parsed_args.uuid)
if data:
if parsed_args.full:
for d in data:
print(json.dumps(d, indent=4, sort_keys=True))
else:
for d in data:
for p in d.get('validation_output', []):
print(json.dumps(p['task'],
indent=4,
sort_keys=True))
else:
raise exceptions.CommandError(
"Could not find the log file linked to this UUID: %s" %
parsed_args.uuid)
class TripleOValidatorShowHistory(command.Lister):
"""Display Validations execution history"""
auth_required = False
def get_parser(self, prog_name):
parser = super(TripleOValidatorShowHistory, self).get_parser(prog_name)
parser.add_argument('--validation',
metavar="<validation>",
type=str,
help='Display execution history for a validation')
return parser
def take_action(self, parsed_args):
actions = ValidationActions(constants.ANSIBLE_VALIDATION_DIR)
return actions.show_history(parsed_args.validation)