Browse Source
This patch is moving the CLI outside of tripleoclient to tripleo-validations repository as an additional tool on top of the main tripleo CLI. Change-Id: I3340fb3b038f1f081bddb26468e4aea9b1ab6883changes/18/766818/6
5 changed files with 876 additions and 0 deletions
@ -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 tripleo_validations 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) |
@ -0,0 +1,35 @@
|
||||
# Copyright 2015 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 os |
||||
|
||||
DEFAULT_VALIDATIONS_BASEDIR = "/usr/share/ansible" |
||||
DEFAULT_VALIDATIONS_LEGACY_BASEDIR = "/usr/share/openstack-tripleo-validations" |
||||
|
||||
VALIDATIONS_LOG_BASEDIR = '/var/log/validations' |
||||
|
||||
ANSIBLE_VALIDATION_DIR = ( |
||||
os.path.join(DEFAULT_VALIDATIONS_LEGACY_BASEDIR, 'playbooks') |
||||
if os.path.exists(os.path.join(DEFAULT_VALIDATIONS_LEGACY_BASEDIR, |
||||
'playbooks')) |
||||
else "/usr/share/ansible/validation-playbooks" |
||||
) |
||||
|
||||
|
||||
VALIDATION_GROUPS_INFO = ( |
||||
'/usr/share/ansible/groups.yaml' |
||||
if os.path.exists('/usr/share/ansible/groups.yaml') |
||||
else os.path.join(DEFAULT_VALIDATIONS_LEGACY_BASEDIR, 'groups.yaml') |
||||
) |
@ -0,0 +1,55 @@
|
||||
# Copyright 2013 Nebula 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. |
||||
# |
||||
|
||||
"""OpenStackClient Plugin interface""" |
||||
|
||||
import logging |
||||
|
||||
from osc_lib import utils |
||||
|
||||
|
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
DEFAULT_TRIPLEO_VALIDATION_API_VERSION = '2' |
||||
|
||||
# Required by the OSC plugin interface |
||||
API_NAME = 'tripleo_validations' |
||||
API_VERSION_OPTION = 'os_tripleo_validations_api_version' |
||||
API_VERSIONS = { |
||||
'2': 'tripleo_validations.plugin' |
||||
} |
||||
|
||||
|
||||
# Required by the OSC plugin interface |
||||
def build_option_parser(parser): |
||||
"""Hook to add global options |
||||
|
||||
Called from openstackclient.shell.OpenStackShell.__init__() |
||||
after the builtin parser has been initialized. This is |
||||
where a plugin can add global options such as an API version setting. |
||||
|
||||
:param argparse.ArgumentParser parser: The parser object that has been |
||||
initialized by OpenStackShell. |
||||
""" |
||||
parser.add_argument( |
||||
'--os-tripleo-validations-api-version', |
||||
metavar='<tripleo-validations-api-version>', |
||||
default=utils.env( |
||||
'OS_TRIPLEO_VALIDATIONS_API_VERSION', |
||||
default=DEFAULT_TRIPLEO_VALIDATION_API_VERSION), |
||||
help=("TripleO Client API version, default={}" |
||||
" (Env: DEFAULT_TRIPLEO_VALIDATION_API_VERSION)").format( |
||||
DEFAULT_TRIPLEO_VALIDATION_API_VERSION)) |
||||
return parser |
@ -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 tripleo_validations 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." |
||||
raise exceptions.CommandError(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) |
Loading…
Reference in new issue