Allow running validations with custom extra variables

This patch adds the ability to pass extra variables to the validations
execution through Mistral and 'ansible-playbook'.

By default, the CLI is running the validations through
'ansible-playbook', then the operator will have two possible ways to
process it;

Either passing a Dict with the new --extra-vars argument

```
$ openstack tripleo validator run \
    --extra-vars '{"min_undercloud_ram_gb": 2}'
    --validation-name undercloud-ram
```

or passing the absolute path of a JSON/YAML file using --extra-vars-file
argument.

```
$ cat /home/stack/vars.json
{
  "volumes": [
    {"min_size": 10, "mount": "/var/lib/docker"},
    {"min_size": 3,  "mount": "/var/lib/config-data"},
    {"min_size": 3,  "mount": "/var/log"},
    {"min_size": 5,  "mount": "/usr"},
    {"min_size": 20, "mount": "/var"},
    {"min_size": 25, "mount": "/"}
  ]
}
$ openstack tripleo validator run \
    --extra-vars-file /home/stack/vars.json
    --validation-name undercloud-disk-space
```

If the operator wants to use Mistral, she/he will just need to add the
--use-mistral argument to the command line in the previous
examples.

Note that Mistral will only support a valid JSON file as input.

Change-Id: Idff6bcebc041db483d64396249b040e6bdd778bd
Signed-off-by: Gael Chamoulaud <gchamoul@redhat.com>
This commit is contained in:
Gael Chamoulaud 2019-07-10 17:15:56 +02:00
parent 35952145a1
commit d13a7d501e
3 changed files with 142 additions and 84 deletions

View File

@ -0,0 +1,8 @@
---
features:
- |
The operator is now able to pass extra variables while executing validations
through the command line. The command line will accept either a Dict with
the new --extra-vars argument or the absolute path of a file (JSON or YAML
when using ansible and JSON only when using Mistral) with the new
--extra-vars-file argument.

View File

@ -67,9 +67,11 @@ class TestValidatorRun(utils.TestCommand):
self.cmd.take_action(parsed_args)
plan_mock.assert_called_once_with(
mock.ANY,
{'plan': 'overcloud',
'validation_names': ['check-ftype']})
mock.ANY, {
'plan': 'overcloud',
'validation_names': ['check-ftype'],
'validation_inputs': {}
})
@mock.patch('logging.getLogger')
@mock.patch('pwd.getpwuid')
@ -104,5 +106,6 @@ class TestValidatorRun(utils.TestCommand):
playbook='check-ftype.yaml',
retries=False,
output_callback='validation_output',
extra_vars={},
python_interpreter='/usr/bin/python{}'.format(sys.version_info[0])
)

View File

@ -14,6 +14,7 @@
#
import argparse
import json
import logging
import os
import pwd
@ -53,6 +54,7 @@ class TripleOValidatorList(command.Command):
parser = argparse.ArgumentParser(
description=self.get_description(),
prog=prog_name,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False
)
@ -61,10 +63,8 @@ class TripleOValidatorList(command.Command):
action='store',
default='table',
choices=['table', 'json', 'yaml'],
help=_("Change the default output. "
"Defaults to: json "
"i.e. --output json"
" --output yaml")
help=_("Change the default output: "
"--output json|yaml")
)
parser.add_argument(
@ -74,10 +74,9 @@ class TripleOValidatorList(command.Command):
default=[],
help=_("List specific group validations, "
"if more than one group is required "
"separate the group names with commas"
"Defaults to: [] "
"i.e. --group pre-upgrade,prep "
" --group openshift-on-openstack")
"separate the group names with commas: "
"--group pre-upgrade,prep | "
"--group openshift-on-openstack")
)
return parser
@ -114,6 +113,7 @@ class TripleOValidatorRun(command.Command):
parser = argparse.ArgumentParser(
description=self.get_description(),
prog=prog_name,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False
)
@ -121,18 +121,39 @@ class TripleOValidatorRun(command.Command):
'--plan',
action='store',
default='overcloud',
help=_("Execute the validations using a "
"custom plan name. "
"Defaults to: overcloud")
help=_("Execute the validations using a custom plan name")
)
parser.add_argument(
'--use-mistral',
action='store_true',
default=False,
help=_("Execute the validations using "
"Mistral. "
"Defaults to: false")
help=_("Execute the validations using Mistral")
)
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."
)
)
ex_group = parser.add_mutually_exclusive_group(required=True)
@ -144,10 +165,9 @@ class TripleOValidatorRun(command.Command):
default=[],
help=_("Run specific validations, "
"if more than one validation is required "
"separate the names with commas"
"Defaults to: [] "
"i.e. --validation-name check-ftype,512e "
" --validation-name 512e")
"separate the names with commas: "
"--validation-name check-ftype,512e | "
"--validation-name 512e")
)
ex_group.add_argument(
@ -157,80 +177,107 @@ class TripleOValidatorRun(command.Command):
default=[],
help=_("Run specific group validations, "
"if more than one group is required "
"separate the group names with commas"
"Defaults to: ['pre-deployment'] "
"i.e. --group pre-upgrade,prep "
" --group openshift-on-openstack")
"separate the group names with commas: "
"--group pre-upgrade,prep | "
"--group openshift-on-openstack")
)
return parser
def _run_validation_run_with_mistral(self, parsed_args):
clients = self.app.client_manager
LOG = logging.getLogger(__name__ + ".ValidationsRunWithMistral")
extra_vars_input = {}
if parsed_args.extra_vars:
extra_vars_input = parsed_args.extra_vars
if parsed_args.extra_vars_file:
try:
with open(parsed_args.extra_vars_file, 'r') as vars_file:
extra_vars_input = json.load(vars_file)
except ValueError as e:
raise RuntimeError(
'Error occured while decoding extra vars JSON file: %s' %
e)
if not parsed_args.validation_name:
workflow_input = {
"plan": parsed_args.plan,
"group_names": parsed_args.group
}
else:
workflow_input = {
"plan": parsed_args.plan,
"validation_names": parsed_args.validation_name,
"validation_inputs": extra_vars_input
}
LOG.debug(_('Running the validations with Mistral'))
output = validations.run_validations(clients, workflow_input)
for out in output:
print('[{}] - {}\n{}'.format(
out.get('status'),
out.get('validation_name'),
oooutils.indent(out.get('stdout'))))
def _run_validator_run(self, parsed_args):
clients = self.app.client_manager
LOG = logging.getLogger(__name__ + ".ValidationsRun")
LOG = logging.getLogger(__name__ + ".ValidationsRunAnsible")
playbooks = []
extra_vars_input = {}
if parsed_args.use_mistral:
if not parsed_args.validation_name:
workflow_input = {
"plan": parsed_args.plan,
"group_names": parsed_args.group
}
else:
workflow_input = {
"plan": parsed_args.plan,
"validation_names": parsed_args.validation_name
}
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:
workflow_input = {
"group_names": parsed_args.group
}
LOG.debug(_('Getting the validations list by group'))
try:
output = validations.list_validations(
clients, workflow_input)
for val in output:
playbooks.append(val.get('id') + '.yaml')
except Exception as e:
print(
_("Validations listing by group finished with errors"))
print('Output: {}'.format(e))
LOG.debug(_('Running the validations with Mistral'))
output = validations.run_validations(clients, workflow_input)
for out in output:
print('[{}] - {}\n{}'.format(
out.get('status'),
out.get('validation_name'),
oooutils.indent(out.get('stdout'))))
else:
if parsed_args.group:
workflow_input = {
"group_names": parsed_args.group
}
for pb in parsed_args.validation_name:
playbooks.append(pb + '.yaml')
LOG.debug(_('Getting the validations list by group'))
try:
output = validations.list_validations(
clients, workflow_input)
for val in output:
playbooks.append(val.get('id') + '.yaml')
except Exception as e:
print(
_("Validations listing by group finished with errors"))
print('Output: {}'.format(e))
python_interpreter = \
"/usr/bin/python{}".format(sys.version_info[0])
else:
for pb in parsed_args.validation_name:
playbooks.append(pb + '.yaml')
python_interpreter = \
"/usr/bin/python{}".format(sys.version_info[0])
for playbook in playbooks:
try:
LOG.debug(_('Running the validations with Ansible'))
rc, output = oooutils.run_ansible_playbook(
logger=LOG,
workdir=constants.ANSIBLE_VALIDATION_DIR,
log_path_dir=pwd.getpwuid(os.getuid()).pw_dir,
playbook=playbook,
inventory='/usr/bin/tripleo-ansible-inventory',
retries=False,
connection='local',
output_callback='validation_output',
python_interpreter=python_interpreter)
print('[SUCCESS] - {}\n{}'.format(
playbook, oooutils.indent(output)))
except Exception as e:
print('[FAILED] - {}\n{}'.format(
playbook, oooutils.indent(e.args[0])))
for playbook in playbooks:
try:
LOG.debug(_('Running the validations with Ansible'))
rc, output = oooutils.run_ansible_playbook(
logger=LOG,
workdir=constants.ANSIBLE_VALIDATION_DIR,
log_path_dir=pwd.getpwuid(os.getuid()).pw_dir,
playbook=playbook,
inventory='/usr/bin/tripleo-ansible-inventory',
retries=False,
connection='local',
output_callback='validation_output',
extra_vars=extra_vars_input,
python_interpreter=python_interpreter)
print('[SUCCESS] - {}\n{}'.format(
playbook, oooutils.indent(output)))
except Exception as e:
print('[FAILED] - {}\n{}'.format(
playbook, oooutils.indent(e.args[0])))
def take_action(self, parsed_args):
self._run_validator_run(parsed_args)
if parsed_args.use_mistral:
self._run_validation_run_with_mistral(parsed_args)
else:
self._run_validator_run(parsed_args)