[cli] Small tweaks

- group subcommands of `rally verify` subcommand
- put proper descirptions for rally categories
- log deprecation warning on `rally deployment` commands execution
- deprecate all --deployment arguments in favor of --env

Change-Id: I94ed1f37e59ae266affe45fc7c8a5077923d40cb
Signed-off-by: Andriy Kurilin <andr.kurilin@gmail.com>
This commit is contained in:
Andriy Kurilin committed 2026-07-12 19:15:05 +02:00
1 parent e75eaa1142
commit 633c3ebfbe
18 files changed
+459 -317

No files matched your search

+12
View File
@@ -53,6 +53,13 @@ Changed
pyflakes and import-convention checks, while flake8 keeps the remaining
hacking and Rally-specific rules.
Deprecated
~~~~~~~~~~
* The ``--deployment`` / ``--deployment-id`` options of ``rally task`` and
``rally verify`` are renamed to ``--env``. The old names still work but now
print a deprecation warning -- switch to ``--env``.
Removed
~~~~~~~
@@ -63,6 +70,11 @@ Removed
* Support for Python 3.9
* ``rally deployment create --fromenv`` no longer special-cases
rally-openstack ``<= 1.4.0`` (it used to drop ``OS_KEY``/``https_key`` for
those). That release is ~7 years old and predates every currently supported
Python -- old rally-openstack only went up to py37 -- so the shim is gone.
Fixed
~~~~~
+14 -14
View File
@@ -46,35 +46,35 @@ _rally()
OPTS["task_abort"]="--soft"
OPTS["task_delete"]="--force"
OPTS["task_detailed"]="--iterations-data --filter-by"
OPTS["task_export"]="--type --to --deployment"
OPTS["task_import"]="--file --deployment --tag"
OPTS["task_list"]="--deployment --all-deployments --status --tag --uuids-only"
OPTS["task_report"]="--out --open --html --html-static --json --deployment"
OPTS["task_restart"]="--deployment --scenario --tag --no-use --abort-on-sla-failure"
OPTS["task_export"]="--type --to --env"
OPTS["task_import"]="--file --env --tag"
OPTS["task_list"]="--env --all-envs --status --tag --uuids-only"
OPTS["task_report"]="--out --open --html --html-static --json --env"
OPTS["task_restart"]="--env --scenario --tag --no-use --abort-on-sla-failure"
OPTS["task_results"]=""
OPTS["task_sla-check"]="--json"
OPTS["task_start"]="--deployment --task-args --task-args-file --tag --no-use --abort-on-sla-failure"
OPTS["task_start"]="--env --task-args --task-args-file --tag --no-use --abort-on-sla-failure"
OPTS["task_status"]=""
OPTS["task_trends"]="--out --open --html-static"
OPTS["task_use"]=""
OPTS["task_validate"]="--deployment --task-args --task-args-file"
OPTS["task_validate"]="--env --task-args --task-args-file"
OPTS["verify_add-verifier-ext"]="--source --extra-settings"
OPTS["verify_configure-verifier"]="--deployment-id --reconfigure --extend --override --show"
OPTS["verify_configure-verifier"]="--env --reconfigure --extend --override --show"
OPTS["verify_create-verifier"]="--name --type --platform --source --system-wide --extra-settings --no-use"
OPTS["verify_delete"]=""
OPTS["verify_delete-verifier"]="--deployment-id --force"
OPTS["verify_delete-verifier"]="--env --force"
OPTS["verify_delete-verifier-ext"]="--name"
OPTS["verify_import"]="--deployment-id --file --run-args --no-use"
OPTS["verify_list"]="--id --deployment-id --tag --status"
OPTS["verify_import"]="--env --file --run-args --no-use"
OPTS["verify_list"]="--id --env --tag --status"
OPTS["verify_list-plugins"]="--platform"
OPTS["verify_list-verifier-exts"]=""
OPTS["verify_list-verifier-tests"]="--pattern"
OPTS["verify_list-verifiers"]="--status"
OPTS["verify_report"]="--type --to --open"
OPTS["verify_rerun"]="--deployment-id --failed --tag --concurrency --detailed --no-use"
OPTS["verify_rerun"]="--env --failed --tag --concurrency --detailed --no-use"
OPTS["verify_show"]="--sort-by --detailed"
OPTS["verify_show-verifier"]=""
OPTS["verify_start"]="--deployment-id --tag --pattern --concurrency --load-list --skip-list --xfail-list --detailed --no-use"
OPTS["verify_start"]="--env --tag --pattern --concurrency --load-list --skip-list --xfail-list --detailed --no-use"
OPTS["verify_update-verifier"]="--update-venv --system-wide --no-system-wide"
OPTS["verify_use"]=""
OPTS["verify_use-verifier"]=""
@@ -105,4 +105,4 @@ _rally()
return 0
}
complete -o filenames -F _rally rally
complete -o filenames -F _rally rally
+93 -7
View File
@@ -29,6 +29,10 @@ import typer
import typer.core
from rally.cli import cliutils
from rally.common import logging
LOG = logging.getLogger(__name__)
if t.TYPE_CHECKING:
@@ -67,6 +71,81 @@ class ArgumentOrKeyword(typer.models.ArgumentInfo):
self.kw_decls = kw_decls
class DeprecatedAlias(typer.models.OptionInfo):
"""A typer Option whose trailing flag names are deprecated aliases.
Declare the preferred flag first, then the deprecated one(s)::
deployment: t.Annotated[
str,
DeprecatedAlias("--env", deprecated=["--deployment"], help="...")
]
This is for value-taking options; for a boolean flag just declare two typer
options (the deprecated one ``hidden=True``) and warn in the command body.
Both flags keep working; passing a deprecated one logs a warning naming the
preferred flag. The deprecated aliases must be listed explicitly via
``deprecated=``; ``param_decls`` holds only the preferred flag(s). Only
the preferred flag lands on the built option (so the deprecated one stays
out of ``--help``); :func:`install` registers each deprecated alias as a
separate parser option feeding the same destination and wraps it to emit
the warning (a plain callback cannot tell which alias was typed -- both
share one handler otherwise).
"""
def __init__(
self,
*param_decls: str,
deprecated: t.Iterable[str],
default: t.Any = ...,
**kwargs: t.Any
) -> None:
# ``default=...`` mirrors ``typer.Option`` (required unless the
# annotated parameter carries its own default). Only the preferred
# flag(s) reach typer, keeping the deprecated ones out of ``--help``.
super().__init__(default=default, param_decls=list(param_decls),
**kwargs)
self.deprecated = list(deprecated)
# the flag we steer users towards must never be a deprecated one
self.preferred = next(d for d in param_decls
if d not in self.deprecated)
def _patch_deprecated(
param: "typer.core.TyperOption",
info: DeprecatedAlias
) -> None:
"""Register the deprecated aliases and warn when one is used."""
original_add_to_parser = param.add_to_parser
def add_to_parser(
parser: "typer._click.core._OptionParser",
ctx: "typer._click.core.Context"
) -> None:
original_add_to_parser(parser, ctx)
# a separate parser option (own handler) feeding the same destination:
# requiredness stays satisfied since both write ``param.name``.
action = "append" if param.nargs == -1 else "store"
parser.add_option(param, info.deprecated, param.name,
action=action, nargs=1)
for opt in info.deprecated:
handler = parser._long_opt.get(opt) or parser._short_opt.get(opt)
if handler is None:
continue
original_process = handler.process
def process(value: t.Any, state: t.Any,
_process: t.Any = original_process,
_opt: str = opt) -> None:
LOG.warning(f"The `{_opt}` option is deprecated; use "
f"`{info.preferred}` instead.")
_process(value, state)
handler.process = process # type: ignore[method-assign]
param.add_to_parser = add_to_parser # type: ignore[method-assign]
def _patch_param(
param: "typer.core.TyperOption",
kw_decls: t.Sequence[str]
@@ -99,18 +178,25 @@ def _patch_param(
def install(command: typer.core.TyperGroup | typer.core.TyperCommand) -> None:
"""Wire up every :class:`ArgumentOrKeyword` in a built command tree."""
"""Wire up :class:`ArgumentOrKeyword` and :class:`DeprecatedAlias` params.
Both are declared as annotation metadata; this walks the built command tree
and patches the corresponding parser options in place.
"""
for _path, leaf, params in cliutils.iter_commands(command):
if leaf.callback is None:
continue
signature = inspect.signature(inspect.unwrap(leaf.callback))
marks = {}
kw_marks = {}
deprecated_marks = {}
for name, parameter in signature.parameters.items():
for meta in getattr(parameter.annotation, "__metadata__", ()):
if isinstance(meta, ArgumentOrKeyword):
marks[name] = meta.kw_decls
if not marks:
continue
kw_marks[name] = meta.kw_decls
elif isinstance(meta, DeprecatedAlias):
deprecated_marks[name] = meta
for param in params:
if param.name in marks:
_patch_param(param, marks[param.name])
if param.name in kw_marks:
_patch_param(param, kw_marks[param.name])
if param.name in deprecated_marks:
_patch_deprecated(param, deprecated_marks[param.name])
+1
View File
@@ -94,6 +94,7 @@ def generate() -> str:
and name not in ("--help", "--version"))
)
for p in params
if not getattr(p, "hidden", False)
))
lines.append(f' OPTS["{category}_{name}"]="{opts}"\n')
+14
View File
@@ -48,6 +48,20 @@ def get_api() -> "API":
return _api.get()
_help_requested: "contextvars.ContextVar[bool]" = contextvars.ContextVar(
"rally_help_requested", default=False)
def set_help_requested(value: bool) -> None:
"""Record whether the current invocation is a ``--help`` call."""
_help_requested.set(value)
def is_help_requested() -> bool:
"""Return True if the current invocation only renders help."""
return _help_requested.get()
def iter_commands(
command: "typer._click.core.Command",
) -> t.Iterator[
+1 -1
View File
@@ -22,7 +22,7 @@ from rally.common import db
db_app = typer.Typer(
name="db", no_args_is_help=False,
help="Set of commands that allow you to manage Rally's database.")
help="Create, upgrade and inspect the database.")
def _print_connection(show_creds: bool) -> None:
+12 -11
View File
@@ -35,9 +35,20 @@ from rally.common import utils
from rally.env import env_mgr
LOG = logging.getLogger(__name__)
deployment_app = typer.Typer(
name="deployment", no_args_is_help=False,
help="Set of commands that allow you to manage deployments.")
help="Legacy environments (superseded by env).")
@deployment_app.callback()
def _deprecation_warning(ctx: typer.Context) -> None:
# warn once per invocation, but not when only rendering help or when no
# subcommand is actually run
if ctx.invoked_subcommand and not cliutils.is_help_requested():
LOG.warning("The `deployment` commands are deprecated; use the `env` "
"commands instead.")
def _list_deployments(api: API,
@@ -186,16 +197,6 @@ def create(
if fromenv:
result = env_mgr.EnvManager.create_spec_from_sys_environ()
config = result["spec"]
if "existing@openstack" in config:
# NOTE(andreykurilin): if we are here it means that
# rally-openstack package is installed
import rally_openstack # type: ignore[import-not-found]
if rally_openstack.__version_tuple__ <= (1, 4, 0):
if ("https_key" in config["existing@openstack"]
and config["existing@openstack"]["https_key"]):
print("WARNING: OS_KEY is ignored due to old version "
"of rally-openstack package.")
config["existing@openstack"].pop("https_key")
else:
if not filename:
config = {}
+1 -1
View File
@@ -28,7 +28,7 @@ from rally.env import env_mgr
env_app = typer.Typer(
name="env", no_args_is_help=False,
help="Set of commands that allow you to manage envs.")
help="Environments Rally tests against.")
YES = u":-)"
NO = u":-("
+1 -1
View File
@@ -27,7 +27,7 @@ from rally.common.plugin import plugin
plugin_app = typer.Typer(
name="plugin", no_args_is_help=False,
help="Set of commands that allow you to manage Rally plugins.")
help="Discover and inspect installed plugins.")
def _print_plugins_list(plugin_list: list) -> None:
+66 -44
View File
@@ -45,7 +45,7 @@ LOG = logging.getLogger(__name__)
task_app = typer.Typer(
name="task", no_args_is_help=False,
help="Set of commands that allow you to manage tasks and results.")
help="Run scenario tests and benchmarks.")
class FailedToLoadTask(exceptions.RallyException):
@@ -435,11 +435,13 @@ def validate(
help="Path to the input task file (or '-' for stdin)."
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
envvar=envutils.ENV_ENV,
help="UUID or name of a deployment."
help="UUID or name of the environment."
)
],
task_args: t.Annotated[
@@ -471,7 +473,7 @@ def validate(
task = _load_and_validate_task(api, task_file, raw_args=task_args,
args_file=task_args_file)
api.task.validate(deployment=deployment, config=task)
api.task.validate(deployment=env, config=task)
print("Input Task is valid :)")
@@ -486,11 +488,13 @@ def start(
help="Path to the input task file (or '-' for stdin)."
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
envvar=envutils.ENV_ENV,
help="UUID or name of a deployment."
help="UUID or name of the environment."
)
],
task_args: t.Annotated[
@@ -544,7 +548,7 @@ def start(
args_file=task_args_file)
print("Running Rally version", version.version_string())
rc = _start_task(api, deployment, task_config=input_task, tags=tags,
rc = _start_task(api, env, task_config=input_task, tags=tags,
do_use=not no_use,
abort_on_sla_failure=abort_on_sla_failure)
if rc:
@@ -554,11 +558,13 @@ def start(
@task_app.command()
@plugins.ensure_plugins_are_loaded
def restart(
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
envvar=envutils.ENV_ENV,
help="UUID or name of a deployment."
help="UUID or name of the environment."
)
],
task_id: t.Annotated[
@@ -636,7 +642,7 @@ def restart(
"workloads": workloads})
if retask["subtasks"]:
rc = _start_task(api, deployment, retask, tags=tags,
rc = _start_task(api, env, retask, tags=tags,
do_use=not no_use,
abort_on_sla_failure=abort_on_sla_failure)
if rc:
@@ -752,25 +758,33 @@ def results(
@task_app.command(name="list")
def list_(
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
envvar=envutils.ENV_ENV,
help="UUID or name of a deployment."
help="UUID or name of the environment."
)
],
all_envs: t.Annotated[
bool,
typer.Option(
"--all-envs",
help="List tasks from all environments."
)
] = False,
all_deployments: t.Annotated[
bool,
typer.Option(
"--all-deployments",
help="List tasks from all deployments."
hidden=True
)
] = False,
status: t.Annotated[
str | None,
status: t.Annotated[ # type: ignore[valid-type]
t.Literal[tuple(consts.TaskStatus)] | None,
typer.Option(
help="List tasks with specified status. Available statuses: %s"
% ", ".join(consts.TaskStatus)
help="List tasks with the specified status."
)
] = None,
tags: t.Annotated[
@@ -790,24 +804,25 @@ def list_(
) -> None:
"""List tasks, started and finished.
Displayed tasks can be filtered by status or deployment. By
Displayed tasks can be filtered by status or environment. By
default 'rally task list' will display tasks from the active
deployment without filtering by status.
environment without filtering by status.
"""
api = cliutils.get_api()
filters: dict = {}
headers = ["UUID", "Deployment name", "Created at", "Load duration",
headers = ["UUID", "Environment", "Created at", "Load duration",
"Status", "Tag(s)"]
if status in consts.TaskStatus:
if status:
filters["status"] = status
elif status:
print("Error: Invalid task status '%s'.\nAvailable statuses: %s"
% (status, ", ".join(consts.TaskStatus)), file=sys.stderr)
raise typer.Exit(code=1)
if not all_deployments:
filters["deployment"] = deployment
if all_deployments:
LOG.warning("The `--all-deployments` option is deprecated; use "
"`--all-envs` instead.")
all_envs = True
if not all_envs:
filters["deployment"] = env
if tags:
filters["tags"] = tags
@@ -825,6 +840,7 @@ def list_(
formatters = {
"Tag(s)": tags_formatter,
"Environment": lambda t: t["deployment_name"],
"Load duration": cliutils.pretty_float_formatter(
"task_duration", 3),
"Created at": lambda t: t["created_at"].replace("T", " ")
@@ -931,10 +947,12 @@ def report(
help="Generate JSON report."
)
] = False,
deployment: t.Annotated[
env: t.Annotated[
str | None,
typer.Option(
help="Report all tasks with defined deployment"
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
help="Report all tasks for the defined environment."
)
] = None,
) -> None:
@@ -942,7 +960,7 @@ def report(
out_format = ("json" if to_json
else "html-static" if html_static else "html")
_export(cliutils.get_api(), tasks=tasks, output_type=out_format,
output_dest=out, open_it=open_it, deployment=deployment)
output_dest=out, open_it=open_it, deployment=env)
@task_app.command()
@@ -1070,16 +1088,18 @@ def export(
"the report type."
)
] = None,
deployment: t.Annotated[
env: t.Annotated[
str | None,
typer.Option(
help="Report all tasks with defined deployment"
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
help="Report all tasks for the defined environment."
)
] = None,
) -> None:
"""Export task results to the custom task's exporting system."""
_export(cliutils.get_api(), tasks=tasks, output_type=output_type,
output_dest=output_dest, deployment=deployment)
output_dest=output_dest, deployment=env)
@task_app.command(name="import")
@@ -1092,11 +1112,13 @@ def import_results(
help="JSON file with task results"
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment"],
envvar=envutils.ENV_ENV,
help="UUID or name of a deployment."
help="UUID or name of the environment."
)
],
tags: t.Annotated[
@@ -1107,12 +1129,12 @@ def import_results(
)
] = None,
) -> None:
"""Import json results of a test into rally database."""
"""Import JSON results of a test into the Rally database."""
if os.path.exists(os.path.expanduser(task_file)):
api = cliutils.get_api()
tasks_results = task_results_loader.load(task_file)
for task_results in tasks_results:
task = api.task.import_results(deployment=deployment,
task = api.task.import_results(deployment=env,
task_results=task_results,
tags=tags)
print("Task UUID: %s." % task["uuid"])
+77 -65
View File
@@ -24,6 +24,7 @@ import webbrowser
import typer
from rally import consts
from rally import exceptions
from rally import plugins
from rally.api import API
@@ -38,8 +39,8 @@ TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
LIST_VERIFIERS_HINT = ("HINT: You can list all verifiers, executing "
"command `rally verify list-verifiers`.")
LIST_DEPLOYMENTS_HINT = ("HINT: You can list all deployments, executing "
"command `rally deployment list`.")
LIST_ENVS_HINT = ("HINT: You can list all environments, executing "
"command `rally env list`.")
LIST_VERIFICATIONS_HINT = ("HINT: You can list all verifications, executing "
"command `rally verify list`.")
@@ -47,9 +48,14 @@ DEFAULT_REPORT_TYPES = ("HTML", "HTML-Static", "JSON", "JUnit-XML")
ACTIVE = u":-)"
# help panels grouping the subcommands in ``rally verify --help``
VERIFIER = "Verifier management"
VERIFIER_EXT = "Verifier extensions"
VERIFICATION = "Verifications"
verify_app = typer.Typer(
name="verify", no_args_is_help=False,
help="Verify an OpenStack cloud via a verifier.")
help="Run external verifier test suites.")
def _print_totals(totals: dict) -> None:
@@ -134,7 +140,7 @@ def list_plugins(
normalize_field_names=True)
@verify_app.command(name="create-verifier")
@verify_app.command(name="create-verifier", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def create_verifier(
name: t.Annotated[
@@ -206,7 +212,7 @@ def create_verifier(
_use_verifier(api, verifier_uuid)
@verify_app.command(name="use-verifier")
@verify_app.command(name="use-verifier", rich_help_panel=VERIFIER)
def use_verifier(
verifier_id: t.Annotated[
str,
@@ -220,11 +226,11 @@ def use_verifier(
_use_verifier(cliutils.get_api(), verifier_id)
@verify_app.command(name="list-verifiers")
@verify_app.command(name="list-verifiers", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def list_verifiers(
status: t.Annotated[
str | None,
status: t.Annotated[ # type: ignore[valid-type]
t.Literal[tuple(consts.VerifierStatus)] | None,
typer.Option(
help="Status to filter verifiers by."
)
@@ -251,7 +257,7 @@ def list_verifiers(
"command `rally verify create-verifier`.")
@verify_app.command(name="show-verifier")
@verify_app.command(name="show-verifier", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def show_verifier(
verifier_id: t.Annotated[
@@ -289,7 +295,7 @@ def show_verifier(
"virtual environment, you do it at your own risk!")
@verify_app.command(name="delete-verifier")
@verify_app.command(name="delete-verifier", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def delete_verifier(
verifier_id: t.Annotated[
@@ -299,13 +305,14 @@ def delete_verifier(
help="Verifier name or UUID. " + LIST_VERIFIERS_HINT
)
],
deployment: t.Annotated[
env: t.Annotated[
str | None,
typer.Option(
"--deployment-id",
help="Deployment name or UUID. If specified, only the "
"deployment-specific data will be deleted for verifier. "
+ LIST_DEPLOYMENTS_HINT
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment-id"],
help="Environment name or UUID. If specified, only the "
"environment-specific data will be deleted for verifier. "
+ LIST_ENVS_HINT
)
] = None,
force: t.Annotated[
@@ -321,11 +328,11 @@ def delete_verifier(
) -> None:
"""Delete a verifier."""
api = cliutils.get_api()
api.verifier.delete(verifier_id=verifier_id, deployment_id=deployment,
api.verifier.delete(verifier_id=verifier_id, deployment_id=env,
force=force)
@verify_app.command(name="update-verifier")
@verify_app.command(name="update-verifier", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def update_verifier(
verifier_id: t.Annotated[
@@ -394,7 +401,7 @@ def update_verifier(
"command to update the config file.")
@verify_app.command(name="configure-verifier")
@verify_app.command(name="configure-verifier", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def configure_verifier(
verifier_id: t.Annotated[
@@ -405,12 +412,13 @@ def configure_verifier(
help="Verifier name or UUID. " + LIST_VERIFIERS_HINT
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
"--deployment-id",
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment-id"],
envvar=envutils.ENV_ENV,
help="Deployment name or UUID. " + LIST_DEPLOYMENTS_HINT
help="Environment name or UUID. " + LIST_ENVS_HINT
)
],
reconfigure: t.Annotated[
@@ -463,7 +471,7 @@ def configure_verifier(
with open(new_configuration) as f:
config = f.read()
api.verifier.override_configuration(verifier_id=verifier_id,
deployment_id=deployment,
deployment_id=env,
new_configuration=config)
else:
options: object = extra_options
@@ -484,7 +492,7 @@ def configure_verifier(
options = yaml.safe_load(extra_options)
config = api.verifier.configure(verifier=verifier_id,
deployment_id=deployment,
deployment_id=env,
extra_options=options,
reconfigure=reconfigure)
@@ -492,7 +500,7 @@ def configure_verifier(
print("\n%s\n" % config.strip())
@verify_app.command(name="list-verifier-tests")
@verify_app.command(name="list-verifier-tests", rich_help_panel=VERIFIER)
@plugins.ensure_plugins_are_loaded
def list_verifier_tests(
verifier_id: t.Annotated[
@@ -522,7 +530,7 @@ def list_verifier_tests(
print("No tests found.")
@verify_app.command(name="add-verifier-ext")
@verify_app.command(name="add-verifier-ext", rich_help_panel=VERIFIER_EXT)
@plugins.ensure_plugins_are_loaded
def add_verifier_ext(
verifier_id: t.Annotated[
@@ -561,7 +569,7 @@ def add_verifier_ext(
version=version, extra_settings=extra)
@verify_app.command(name="list-verifier-exts")
@verify_app.command(name="list-verifier-exts", rich_help_panel=VERIFIER_EXT)
@plugins.ensure_plugins_are_loaded
def list_verifier_exts(
verifier_id: t.Annotated[
@@ -586,7 +594,7 @@ def list_verifier_exts(
"extension, using command `rally verify add-verifier-ext`.")
@verify_app.command(name="delete-verifier-ext")
@verify_app.command(name="delete-verifier-ext", rich_help_panel=VERIFIER_EXT)
@plugins.ensure_plugins_are_loaded
def delete_verifier_ext(
verifier_id: t.Annotated[
@@ -609,7 +617,7 @@ def delete_verifier_ext(
api.verifier.delete_extension(verifier_id=verifier_id, name=name)
@verify_app.command(name="start")
@verify_app.command(name="start", rich_help_panel=VERIFICATION)
@plugins.ensure_plugins_are_loaded
def start(
verifier_id: t.Annotated[
@@ -620,12 +628,13 @@ def start(
help="Verifier name or UUID. " + LIST_VERIFIERS_HINT
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
"--deployment-id",
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment-id"],
envvar=envutils.ENV_ENV,
help="Deployment name or UUID. " + LIST_DEPLOYMENTS_HINT
help="Environment name or UUID. " + LIST_ENVS_HINT
)
],
tags: t.Annotated[
@@ -729,7 +738,7 @@ def start(
try:
results = api.verification.start(
verifier_id=verifier_id, deployment_id=deployment,
verifier_id=verifier_id, deployment_id=env,
tags=tags, **run_args)
verification_uuid = results["verification"]["uuid"]
except exceptions.DeploymentNotFinishedStatus as e:
@@ -753,7 +762,7 @@ def start(
raise typer.Exit(code=3)
@verify_app.command(name="use")
@verify_app.command(name="use", rich_help_panel=VERIFICATION)
def use(
verification_uuid: t.Annotated[
str,
@@ -767,7 +776,7 @@ def use(
_use(cliutils.get_api(), verification_uuid)
@verify_app.command(name="rerun")
@verify_app.command(name="rerun", rich_help_panel=VERIFICATION)
@plugins.ensure_plugins_are_loaded
def rerun(
verification_uuid: t.Annotated[
@@ -778,12 +787,13 @@ def rerun(
help="Verification UUID. " + LIST_VERIFICATIONS_HINT
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
"--deployment-id",
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment-id"],
envvar=envutils.ENV_ENV,
help="Deployment name or UUID. " + LIST_DEPLOYMENTS_HINT
help="Environment name or UUID. " + LIST_ENVS_HINT
)
],
failed: t.Annotated[
@@ -827,7 +837,7 @@ def rerun(
"""Rerun tests from a verification for a specific deployment."""
api = cliutils.get_api()
results = api.verification.rerun(verification_uuid=verification_uuid,
deployment_id=deployment,
deployment_id=env,
failed=failed,
tags=tags,
concurrency=concur)
@@ -842,7 +852,7 @@ def rerun(
print("Verification UUID: %s." % results["verification"]["uuid"])
@verify_app.command(name="show")
@verify_app.command(name="show", rich_help_panel=VERIFICATION)
def show(
verification_uuid: t.Annotated[
str,
@@ -854,7 +864,7 @@ def show(
],
sort_by: t.Annotated[
t.Literal["name", "duration", "status"],
typer.Option(help="Sort tests by 'name', 'duration' or 'status'.")
typer.Option(help="Sort tests.")
] = "name",
detailed: t.Annotated[
bool,
@@ -889,7 +899,7 @@ def show(
# Main table
fields = ["UUID", "Status", "Started at", "Finished at", "Duration",
"Run arguments", "Tags", "Verifier name", "Verifier type",
"Deployment name", "Tests count", "Tests duration, sec",
"Environment", "Tests count", "Tests duration, sec",
"Success", "Skipped", "Expected failures",
"Unexpected success", "Failures"]
formatters = {
@@ -905,7 +915,7 @@ def show(
"Verifier type": (
lambda v: "%s (platform: %s)" % (verifier["type"],
verifier["platform"])),
"Deployment name": (
"Environment": (
lambda v: "%s (UUID: %s)" % (deployment["name"],
deployment["uuid"])),
"Tests duration, sec": lambda v: v["tests_duration"]
@@ -937,7 +947,7 @@ def show(
print("\nCongratulations! Verification passed all tests ;)")
@verify_app.command(name="list")
@verify_app.command(name="list", rich_help_panel=VERIFICATION)
def list_(
verifier_id: t.Annotated[
str | None,
@@ -946,11 +956,12 @@ def list_(
help="Verifier name or UUID. " + LIST_VERIFIERS_HINT
)
] = None,
deployment: t.Annotated[
env: t.Annotated[
str | None,
typer.Option(
"--deployment-id",
help="Deployment name or UUID. " + LIST_DEPLOYMENTS_HINT
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment-id"],
help="Environment name or UUID. " + LIST_ENVS_HINT
)
] = None,
tags: t.Annotated[
@@ -960,8 +971,8 @@ def list_(
help="Tags to filter verifications by."
)
] = None,
status: t.Annotated[
str | None,
status: t.Annotated[ # type: ignore[valid-type]
t.Literal[tuple(consts.VerificationStatus)] | None,
typer.Option(
help="Status to filter verifications by."
)
@@ -970,16 +981,16 @@ def list_(
"""List all verifications."""
api = cliutils.get_api()
verifications = api.verification.list(verifier_id=verifier_id,
deployment_id=deployment,
deployment_id=env,
tags=tags, status=status)
if verifications:
fields = ["UUID", "Tags", "Verifier name", "Deployment name",
fields = ["UUID", "Tags", "Verifier name", "Environment",
"Started at", "Finished at", "Duration", "Status"]
formatters = {
"Tags": lambda v: ", ".join(v["tags"]) or "-",
"Verifier name": (lambda v: api.verifier.get(
verifier_id=v["verifier_uuid"])["name"]),
"Deployment name": (lambda v: api.deployment.get(
"Environment": (lambda v: api.deployment.get(
deployment=v["deployment_uuid"])["name"]),
"Started at": lambda v: v["created_at"],
"Finished at": lambda v: v["updated_at"],
@@ -989,14 +1000,14 @@ def list_(
}
cliutils.print_list(verifications, fields, formatters=formatters,
normalize_field_names=True, sortby_index=4)
elif verifier_id or deployment or status or tags:
elif verifier_id or env or status or tags:
print("There are no verifications that meet specified criteria.")
else:
print("There are no verifications. You can start verification, "
"using command `rally verify start`.")
@verify_app.command(name="delete")
@verify_app.command(name="delete", rich_help_panel=VERIFICATION)
def delete(
verification_uuid: t.Annotated[
list[str],
@@ -1012,7 +1023,7 @@ def delete(
api.verification.delete(verification_uuid=v_uuid)
@verify_app.command(name="report")
@verify_app.command(name="report", rich_help_panel=VERIFICATION)
@plugins.ensure_plugins_are_loaded
def report(
verification_uuid: t.Annotated[
@@ -1081,7 +1092,7 @@ def report(
print("\n%s\n%s" % (cliutils.make_header(h, len(h)), result["print"]))
@verify_app.command(name="import")
@verify_app.command(name="import", rich_help_panel=VERIFICATION)
@plugins.ensure_plugins_are_loaded
def import_results(
verifier_id: t.Annotated[
@@ -1092,12 +1103,13 @@ def import_results(
help="Verifier name or UUID. " + LIST_VERIFIERS_HINT
)
],
deployment: t.Annotated[
env: t.Annotated[
str,
typer.Option(
"--deployment-id",
argutils.DeprecatedAlias(
"--env",
deprecated=["--deployment-id"],
envvar=envutils.ENV_ENV,
help="Deployment name or UUID. " + LIST_DEPLOYMENTS_HINT
help="Environment name or UUID. " + LIST_ENVS_HINT
)
],
file_to_parse: t.Annotated[
@@ -1133,7 +1145,7 @@ def import_results(
parsed_run_args = yaml.safe_load(run_args) if run_args else {}
verification, results = api.verification.import_results(
verifier_id=verifier_id, deployment_id=deployment,
verifier_id=verifier_id, deployment_id=env,
data=data, **parsed_run_args)
_print_totals(results["totals"])
+12 -7
View File
@@ -51,12 +51,15 @@ if t.TYPE_CHECKING:
app = typer.Typer(name="rally", help="Rally command-line interface.",
no_args_is_help=False, add_completion=True)
app.add_typer(db_app, name="db")
app.add_typer(deployment_app, name="deployment")
app.add_typer(env_app, name="env")
app.add_typer(plugin_app, name="plugin")
app.add_typer(task_app, name="task")
app.add_typer(verify_app, name="verify")
# the command categories are grouped in their own help panel, separate from
# the top-level ``version`` command
_CATEGORIES = "Categories"
app.add_typer(db_app, name="db", rich_help_panel=_CATEGORIES)
app.add_typer(deployment_app, name="deployment", rich_help_panel=_CATEGORIES)
app.add_typer(env_app, name="env", rich_help_panel=_CATEGORIES)
app.add_typer(plugin_app, name="plugin", rich_help_panel=_CATEGORIES)
app.add_typer(task_app, name="task", rich_help_panel=_CATEGORIES)
app.add_typer(verify_app, name="verify", rich_help_panel=_CATEGORIES)
@app.command(name="version")
@@ -136,7 +139,9 @@ def bootstrap(
**kwargs: t.Any,
) -> None:
"""Build the API and expose it to the sub-commands."""
if any(opt in sys.argv[1:] for opt in ctx.help_option_names):
is_help = any(opt in sys.argv[1:] for opt in ctx.help_option_names)
cliutils.set_help_requested(is_help)
if is_help:
return
envutils.load_globals()
config_args: list[str] = []
+4
View File
@@ -393,6 +393,10 @@ def build_cli_params() -> list:
# ``None`` and lets oslo apply its own default when it re-parses argv.
default = opt.default if isinstance(opt, cfg.BoolOpt) else None
help_text = (opt.help or "").strip().split("\n")[0]
# oslo help uses printf placeholders for its own formatter; typer
# shows them raw, so resolve ``%(default)s`` and unescape ``%%``.
help_text = help_text.replace("%(default)s", str(opt.default))
help_text = help_text.replace("%%", "%")
option = typer.Option("--%s" % opt.name, help=help_text)
params.append(
inspect.Parameter(
+47 -67
View File
@@ -89,11 +89,6 @@ class TaskTestCase(unittest.TestCase):
]
}
def _get_deployment_uuid(self, output):
return re.search(
r"Using deployment: (?P<uuid>[0-9a-f\-]{36})",
output).group("uuid")
def _get_task_uuid(self, output):
return re.search(
r"\trally task report (?P<uuid>[0-9a-f\-]{36})",
@@ -505,19 +500,19 @@ class TaskTestCase(unittest.TestCase):
rapi = api.API(config_file=rally.config_filename)
task = rapi.task.get(task_id=task_uuid)
actual = rally("task list --deployment MAIN")
actual = rally("task list --env MAIN")
duration = "%s" % round(task["task_duration"], 3)
duration += " " * (13 - len(duration))
expected = (
"+--------------------------------------+-----------------+"
"+--------------------------------------+-------------+"
"---------------------+---------------+----------+--------+\n"
"| UUID | Deployment name "
"| UUID | Environment "
"| Created at | Load duration | Status | Tag(s) |\n"
"+--------------------------------------+-----------------+"
"+--------------------------------------+-------------+"
"---------------------+---------------+----------+--------+\n"
"| %(uuid)s | MAIN | %(created_at)s "
"| %(duration)s | finished | 'foo' |\n"
"+--------------------------------------+-----------------+"
"| %(uuid)s | MAIN "
"| %(created_at)s | %(duration)s | finished | 'foo' |\n"
"+--------------------------------------+-------------+"
"---------------------+---------------+----------+--------+\n" % {
"uuid": task_uuid,
"created_at": task["created_at"].replace("T", " "),
@@ -536,7 +531,7 @@ class TaskTestCase(unittest.TestCase):
self.assertIn("finished", rally("task list --status finished"))
self.assertIn(
"Deployment name", rally("task list --all-deployments"))
"Environment", rally("task list --all-envs"))
self.assertRaises(utils.RallyCliError,
rally, "task list --status not_existing_status")
@@ -554,7 +549,13 @@ class TaskTestCase(unittest.TestCase):
task_uuid = self._get_task_uuid(res)
self.assertEqual(
task_uuid,
rally("task list --uuids-only --deployment MAIN").strip())
rally("task list --uuids-only --env MAIN").strip())
# the deprecated --deployment alias still works; no_logs sends its
# stderr warning to /dev/null so it can't pollute the compared output
self.assertEqual(
task_uuid,
rally("task list --uuids-only --deployment MAIN",
no_logs=True).strip())
self.assertIn("finished", rally("task status --uuid %s" % task_uuid))
# Validate against multiple tasks
@@ -565,9 +566,9 @@ class TaskTestCase(unittest.TestCase):
task_uuids.append(task_uuid)
self.assertIn("finished",
rally("task status --uuid %s" % task_uuid))
res = rally("task list --uuids-only --deployment MAIN")
res = rally("task list --uuids-only --env MAIN")
self.assertEqual(set(task_uuids), set(res.strip().split("\n")))
res2 = rally("task list --uuids-only --deployment MAIN "
res2 = rally("task list --uuids-only --env MAIN "
"--status finished")
self.assertEqual(res, res2)
@@ -580,25 +581,21 @@ class TaskTestCase(unittest.TestCase):
def test_validate_is_invalid(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
cfg = {"invalid": "config"}
config = utils.TaskConfig(cfg)
self.assertRaises(utils.RallyCliError,
rally,
("task validate --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
f"task validate --task {config.filename} "
f"--env {env_id}")
def test_start(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
output = rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
output = rally(f"task start --task {config.filename} "
f"--env {env_id}")
result = re.search(
r"(?P<task_id>[0-9a-f\-]{36}): started", output)
self.assertIsNotNone(result)
@@ -640,12 +637,10 @@ class TaskTestCase(unittest.TestCase):
def _test_start_abort_on_sla_failure_success(self, cfg, times):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
config = utils.TaskConfig(cfg)
rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s --abort-on-sla-failure") %
{"task_file": config.filename,
"deployment_id": deployment_id})
rally(f"task start --task {config.filename} "
f"--env {env_id} --abort-on-sla-failure")
results = rally("task results", getjson=True)
iterations_completed = len(results[0]["result"])
self.assertEqual(times, iterations_completed)
@@ -714,14 +709,12 @@ class TaskTestCase(unittest.TestCase):
def _test_start_abort_on_sla_failure(self, cfg, times):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
config = utils.TaskConfig(cfg)
self.assertRaises(utils.RallyCliError, rally,
("task start --task %(task_file)s "
"--deployment %(deployment_id)s "
"--abort-on-sla-failure") %
{"task_file": config.filename,
"deployment_id": deployment_id})
f"task start --task {config.filename} "
f"--env {env_id} "
f"--abort-on-sla-failure")
results = rally("task results", getjson=True)
self.assertEqual(1, len(results),
"Second subtask should not be started")
@@ -913,12 +906,9 @@ class TaskTestCase(unittest.TestCase):
self._test_start_abort_on_sla_failure(cfg, times)
def _start_task_in_new_thread(self, rally, cfg, suffix):
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
config = utils.TaskConfig(cfg)
cmd = (("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
cmd = f"task start --task {config.filename} --env {env_id}"
report_path = rally.gen_report_path(suffix=suffix)
task = threading.Thread(target=rally, args=(cmd, ),
kwargs={"report_path": report_path})
@@ -993,12 +983,10 @@ class TaskTestCase(unittest.TestCase):
def test_use(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
config = utils.TaskConfig(self._get_sample_task_config())
output = rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
output = rally(f"task start --task {config.filename} "
f"--env {env_id}")
result = re.search(
r"(?P<uuid>[0-9a-f\-]{36}): started", output)
uuid = result.group("uuid")
@@ -1008,13 +996,11 @@ class TaskTestCase(unittest.TestCase):
def test_start_v2(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
cfg = self._get_sample_task_config_v2()
config = utils.TaskConfig(cfg)
output = rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
output = rally(f"task start --task {config.filename} "
f"--env {env_id}")
result = re.search(
r"(?P<task_id>[0-9a-f\-]{36}): started", output)
self.assertIsNotNone(result)
@@ -1054,13 +1040,11 @@ class TaskTestCase(unittest.TestCase):
def test_restart(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
output = rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
output = rally(f"task start --task {config.filename} "
f"--env {env_id}")
output = rally("task restart")
result = re.search(
@@ -1069,13 +1053,11 @@ class TaskTestCase(unittest.TestCase):
def test_restart_with_scenario(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
output = rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
output = rally(f"task start --task {config.filename} "
f"--env {env_id}")
output = rally(
"task restart --scenario Dummy.dummy_random_fail_in_atomic")
@@ -1088,13 +1070,11 @@ class TaskTestCase(unittest.TestCase):
def test_restart_with_fake_scenario(self):
rally = utils.Rally()
deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env)
env_id = utils.get_global("RALLY_ENV", rally.env)
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
rally(("task start --task %(task_file)s "
"--deployment %(deployment_id)s") %
{"task_file": config.filename,
"deployment_id": deployment_id})
rally(f"task start --task {config.filename} "
f"--env {env_id}")
self.assertRaises(utils.RallyCliError, rally,
"task restart --scenario fake.fake_scenario")
+4 -4
View File
@@ -62,11 +62,11 @@ class Rally(object):
Usage:
rally = Rally()
rally("deployment", "create", "--name", "Some Deployment Name")
output = rally("deployment list")
rally("env", "create", "--name", "Some Environment Name")
output = rally("env list")
"""
_DEPLOYMENT_CREATE_ARGS = ""
_ENV_CREATE_ARGS = ""
def __init__(self, force_new_db=False, plugin_path=None):
@@ -112,7 +112,7 @@ class Rally(object):
self.reports_root = os.environ.get("REPORTS_ROOT",
"rally-cli-output-files")
self._created_files = []
self("deployment create --name MAIN%s" % self._DEPLOYMENT_CREATE_ARGS,
self(f"env create --name MAIN{self._ENV_CREATE_ARGS}",
write_report=False)
def __del__(self):
+17 -36
View File
@@ -15,6 +15,7 @@
import collections
import os
import sys
import tempfile
from unittest import mock
@@ -37,6 +38,19 @@ class DeploymentCommandsTestCase(test.CLITestCase):
extras={}, config={}, spec=spec or {}, platforms=[])
return db.env_get(name)
def test_deprecation_warning(self):
# running a subcommand warns that ``deployment`` is deprecated...
result = self.invoke(["deployment", "list"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("deprecated", result.stderr)
# ...but merely rendering help does not (bootstrap flags --help via
# sys.argv)
with mock.patch.object(sys, "argv", ["rally", "deployment", "--help"]):
result = self.invoke(["deployment", "--help"])
self.assertEqual(0, result.exit_code, result.output)
self.assertNotIn("deprecated", result.stderr)
@mock.patch("rally.api._Deployment.create")
def test_create(self, mock_create):
mock_create.return_value = CREATED
@@ -75,39 +89,6 @@ class DeploymentCommandsTestCase(test.CLITestCase):
mock_create.assert_called_once_with(
config={"auth_url": "http://fake"}, name="from_env")
@mock.patch("rally.api._Deployment.create")
@mock.patch("rally.env.env_mgr.EnvManager.create_spec_from_sys_environ")
def test_create_fromenv_openstack(self, mock_create_spec_from_sys_environ,
mock_create):
mock_create.return_value = CREATED
mock_create_spec_from_sys_environ.side_effect = lambda: {
"spec": {
"existing@openstack": {
"https_key": "some key",
"another_key": "another"
}
}
}
mock_rally_os = mock.Mock()
# OS_KEY is dropped for old rally-openstack, kept for newer ones
for version, expected in (
((1, 4, 0), {"existing@openstack": {"another_key": "another"}}),
((1, 5, 0), {"existing@openstack": {"another_key": "another",
"https_key": "some key"}}),
):
with self.subTest(version=version):
mock_create.reset_mock()
mock_rally_os.__version_tuple__ = version
with mock.patch.dict("sys.modules",
{"rally_openstack": mock_rally_os}):
result = self.invoke([
"deployment", "create", "--name", "from_env",
"--fromenv"])
self.assertEqual(0, result.exit_code, result.output)
mock_create.assert_called_once_with(config=expected,
name="from_env")
@mock.patch("rally.api._Deployment.create")
def test_create_and_use(self, mock_create):
mock_create.return_value = CREATED
@@ -200,7 +181,7 @@ class DeploymentCommandsTestCase(test.CLITestCase):
" demo | RegionOne | internal |\n"
"+--------------------------+----------+----------+"
"-------------+-------------+---------------+\n",
result.output)
result.stdout)
@mock.patch("rally.api._Deployment.get")
def test_use(self, mock_get):
@@ -316,7 +297,7 @@ class DeploymentCommandsTestCase(test.CLITestCase):
"---------------\n\n"
"Error while checking users credentials:\n"
"\tProviderError: No money - no funny!",
result.output.strip())
result.stdout.strip())
@mock.patch("rally.cli.commands.deployment.logging.is_debug",
return_value=True)
@@ -342,4 +323,4 @@ class DeploymentCommandsTestCase(test.CLITestCase):
"Error while checking admin credentials:\n"
"file1\n\tline1\n\n"
"KeystoneError: connection refused",
result.output.strip())
result.stdout.strip())
+60 -36
View File
@@ -129,15 +129,33 @@ class TaskCommandsTestCase(test.CLITestCase):
):
with self.subTest(extra=extra):
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"], *extra])
"--env", env["uuid"], *extra])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn(expected, result.output)
@mock.patch("rally.api._Task.validate")
def test_env_alias_deprecation(self, mock_validate):
env = self._create_env()
task_file = self._write("{'a': 1}")
# the preferred --env flag does not warn
result = self.invoke(["task", "validate", task_file,
"--env", env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
self.assertNotIn("deprecated", result.stderr)
# the legacy --deployment alias still works but warns towards --env
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("deprecated", result.stderr)
self.assertIn("--env", result.stderr)
def test__load_task_wrong_task_args_file(self):
env = self._create_env()
task_file = self._write("{}")
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"],
"--env", env["uuid"],
"--task-args-file", "/no/such/args"])
self.assertEqual(task.FailedToLoadTask.error_code, result.exit_code)
self.assertIn("Invalid --task-args-file passed", result.output)
@@ -148,7 +166,7 @@ class TaskCommandsTestCase(test.CLITestCase):
for raw_args in ("{'test': {}", "foo"):
with self.subTest(raw_args=raw_args):
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"],
"--env", env["uuid"],
"--task-args", raw_args])
self.assertEqual(task.FailedToLoadTask.error_code,
result.exit_code)
@@ -158,7 +176,7 @@ class TaskCommandsTestCase(test.CLITestCase):
env = self._create_env()
task_file = self._write("{}")
bad_args = self._write("{'a': {}")
result = self.invoke(["task", "validate", task_file, "--deployment",
result = self.invoke(["task", "validate", task_file, "--env",
env["uuid"], "--task-args-file", bad_args])
self.assertEqual(task.FailedToLoadTask.error_code, result.exit_code)
self.assertIn("has to be YAML or JSON", result.output)
@@ -167,7 +185,7 @@ class TaskCommandsTestCase(test.CLITestCase):
env = self._create_env()
task_file = self._write("{'test': {{t}}}")
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(task.FailedToLoadTask.error_code, result.exit_code)
self.assertIn("Failed to render task template", result.output)
@@ -176,7 +194,7 @@ class TaskCommandsTestCase(test.CLITestCase):
# renders fine (no jinja) but is not valid YAML/JSON
task_file = self._write("{'test': {}")
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(task.FailedToLoadTask.error_code, result.exit_code)
self.assertIn("Wrong format of rendered input task", result.output)
@@ -204,7 +222,7 @@ class TaskCommandsTestCase(test.CLITestCase):
def test__load_and_validate_file_failed(self):
env = self._create_env()
result = self.invoke(["task", "validate", "/no/such/task",
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(task.FailedToLoadTask.error_code, result.exit_code)
self.assertIn("Error reading /no/such/task", result.output)
@@ -219,7 +237,7 @@ class TaskCommandsTestCase(test.CLITestCase):
"pass_sla": True, "subtasks": []}
result = self.invoke(["task", "start", task_file,
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("started", result.output)
@@ -237,7 +255,7 @@ class TaskCommandsTestCase(test.CLITestCase):
status=consts.DeployStatus.DEPLOY_INIT)
result = self.invoke(["task", "start", task_file,
"--deployment", env["uuid"], "--tag", "some"])
"--env", env["uuid"], "--tag", "some"])
self.assertEqual(1, result.exit_code, result.output)
self.assertIn("unfinished deployment", result.output)
@@ -254,7 +272,7 @@ class TaskCommandsTestCase(test.CLITestCase):
"pass_sla": True, "subtasks": []}
result = self.invoke(["task", "start", task_file,
"--deployment", env["uuid"],
"--env", env["uuid"],
"--task-args-file", args_file, "--tag", "t"])
self.assertEqual(0, result.exit_code, result.output)
@@ -270,7 +288,7 @@ class TaskCommandsTestCase(test.CLITestCase):
mock_create.side_effect = exceptions.InvalidTaskException("foo")
result = self.invoke(["task", "start", task_file,
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(exceptions.InvalidTaskException.error_code,
result.exit_code)
@@ -286,7 +304,7 @@ class TaskCommandsTestCase(test.CLITestCase):
mock_get.return_value = {"uuid": "new-uuid", "pass_sla": False,
"status": "finished", "subtasks": []}
result = self.invoke(["task", "start", task_file, "--deployment",
result = self.invoke(["task", "start", task_file, "--env",
env["uuid"], "--no-use"])
self.assertEqual(2, result.exit_code, result.output)
@@ -306,7 +324,7 @@ class TaskCommandsTestCase(test.CLITestCase):
"runner_type": "constant",
"runner": {"times": 1}, "hooks": [],
"sla": {}}]}]}
args = ["task", "restart", "--deployment", "dep",
args = ["task", "restart", "--env", "dep",
"--uuid", "task-uuid"]
if scenario:
args += ["--scenario", scenario]
@@ -326,7 +344,7 @@ class TaskCommandsTestCase(test.CLITestCase):
"description": "d", "tags": [], "subtasks": [],
"validation_result": {"trace": {}, "etype": "E", "msg": "m"}}
result = self.invoke(["task", "restart", "--deployment", "dep",
result = self.invoke(["task", "restart", "--env", "dep",
"--uuid", "task-uuid"])
self.assertEqual(1, result.exit_code, result.output)
@@ -343,7 +361,7 @@ class TaskCommandsTestCase(test.CLITestCase):
"runner_type": "c", "runner": {}, "hooks": [],
"sla": {}}]}]}
result = self.invoke(["task", "restart", "--deployment", "dep",
result = self.invoke(["task", "restart", "--env", "dep",
"--uuid", "task-uuid"])
self.assertEqual(2, result.exit_code, result.output)
@@ -358,7 +376,7 @@ class TaskCommandsTestCase(test.CLITestCase):
with mock.patch("rally.cli.commands.task.logging.is_debug",
return_value=True):
result = self.invoke(["task", "restart", "--deployment", "dep",
result = self.invoke(["task", "restart", "--env", "dep",
"--uuid", "task-uuid"])
self.assertEqual(1, result.exit_code, result.output)
@@ -515,7 +533,7 @@ class TaskCommandsTestCase(test.CLITestCase):
mock_export.return_value = {"print": "the report body"}
# --deployment lists the tasks itself; the positional is still required
result = self.invoke(["task", "report", "ignored", "--deployment",
result = self.invoke(["task", "report", "ignored", "--env",
"dep", "--out", "out"])
self.assertEqual(0, result.exit_code, result.output)
@@ -557,7 +575,7 @@ class TaskCommandsTestCase(test.CLITestCase):
env = self._create_env()
task_obj = self._create_task(env=env, tags=["d"])
result = self.invoke(["task", "list", "--deployment", env["uuid"]])
result = self.invoke(["task", "list", "--env", env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn(task_obj["uuid"], result.output)
@@ -567,23 +585,24 @@ class TaskCommandsTestCase(test.CLITestCase):
env = self._create_env()
task_obj = self._create_task(env=env)
result = self.invoke(["task", "list", "--deployment", env["uuid"],
result = self.invoke(["task", "list", "--env", env["uuid"],
"--uuids-only"])
self.assertEqual(0, result.exit_code, result.output)
self.assertEqual("%s\n" % task_obj["uuid"], result.stdout)
def test_list_wrong_status(self):
result = self.invoke(["task", "list", "--deployment", "fake",
"--status", "wrong non existing status"])
# typer rejects an unknown status natively (the option is a Literal)
result = self.invoke(["task", "list", "--env", "fake",
"--status", "bogus"])
self.assertEqual(1, result.exit_code, result.output)
self.assertIn("Invalid task status", result.output)
self.assertEqual(2, result.exit_code, result.output)
self.assertIn("Invalid value", result.output)
def test_list_no_results(self):
env = self._create_env()
result = self.invoke(["task", "list", "--deployment", env["uuid"]])
result = self.invoke(["task", "list", "--env", env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("There are no tasks", result.output)
@@ -592,24 +611,29 @@ class TaskCommandsTestCase(test.CLITestCase):
env = self._create_env()
self._create_task(env=env) # a task with no tags
with self.subTest("no-tag task lists with an empty tag cell"):
result = self.invoke(["task", "list", "--deployment",
result = self.invoke(["task", "list", "--env",
env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
with self.subTest("valid status filter with no matches"):
result = self.invoke(["task", "list", "--deployment",
result = self.invoke(["task", "list", "--env",
env["uuid"], "--status", "finished"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("no tasks in 'finished' status", result.output)
with self.subTest("all deployments + tag filter"):
result = self.invoke(["task", "list", "--deployment",
env["uuid"], "--all-deployments",
"--tag", "x"])
with self.subTest("all envs + tag filter"):
result = self.invoke(["task", "list", "--env",
env["uuid"], "--all-envs", "--tag", "x"])
self.assertEqual(0, result.exit_code, result.output)
with self.subTest("deprecated --all-deployments still works, warns"):
result = self.invoke(["task", "list", "--env",
env["uuid"], "--all-deployments"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("deprecated", result.stderr)
self.assertIn("--all-envs", result.stderr)
def test_list_uuids_only_empty(self):
env = self._create_env()
result = self.invoke(["task", "list", "--deployment", env["uuid"],
result = self.invoke(["task", "list", "--env", env["uuid"],
"--uuids-only"])
self.assertEqual(0, result.exit_code, result.output)
@@ -667,7 +691,7 @@ class TaskCommandsTestCase(test.CLITestCase):
task_file = self._write('{"some": "json"}')
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("Input Task is valid", result.output)
@@ -677,7 +701,7 @@ class TaskCommandsTestCase(test.CLITestCase):
def test_validate_failed_to_load_task(self):
env = self._create_env()
result = self.invoke(["task", "validate", "/no/such/task",
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(task.FailedToLoadTask.error_code, result.exit_code)
@@ -688,7 +712,7 @@ class TaskCommandsTestCase(test.CLITestCase):
mock_validate.side_effect = exceptions.InvalidTaskException("foo")
result = self.invoke(["task", "validate", task_file,
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(exceptions.InvalidTaskException.error_code,
result.exit_code)
@@ -746,13 +770,13 @@ class TaskCommandsTestCase(test.CLITestCase):
mock_import_results.return_value = {"uuid": "new-uuid"}
result = self.invoke(["task", "import", "--file", results_file,
"--deployment", env["uuid"], "--tag", "tag"])
"--env", env["uuid"], "--tag", "tag"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("Task UUID: new-uuid", result.output)
mock_import_results.assert_called_once_with(
deployment=env["uuid"], task_results="results", tags=["tag"])
result = self.invoke(["task", "import", "--file", "/no/such/file",
"--deployment", env["uuid"]])
"--env", env["uuid"]])
self.assertEqual(1, result.exit_code, result.output)
self.assertIn("Invalid file name", result.output)
+23 -23
View File
@@ -127,8 +127,8 @@ class VerifyCommandsTestCase(test.CLITestCase):
def test_list_verifiers_empty_verifiers(self):
for args, expected in (
([], "There are no verifiers."),
(["--status", "foo"],
"There are no verifiers with status 'foo'."),
(["--status", "installed"],
"There are no verifiers with status 'installed'."),
):
with self.subTest(args=args):
result = self.invoke(["verify", "list-verifiers", *args])
@@ -229,7 +229,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
# --override cannot be combined with --reconfigure/--extend
result = self.invoke([
"verify", "configure-verifier", verifier.uuid,
"--deployment-id", env["uuid"], "--override", "/p/a/t/h",
"--env", env["uuid"], "--override", "/p/a/t/h",
"--reconfigure", "--show"])
self.assertEqual(1, result.exit_code, result.output)
self.assertFalse(mock_configure.called)
@@ -237,7 +237,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
# a missing --override file is reported and exits 1
result = self.invoke([
"verify", "configure-verifier", verifier.uuid,
"--deployment-id", env["uuid"], "--override", "/p/a/t/h",
"--env", env["uuid"], "--override", "/p/a/t/h",
"--show"])
self.assertEqual(1, result.exit_code, result.output)
self.assertIn("not found", result.output)
@@ -248,7 +248,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
tf.flush()
result = self.invoke([
"verify", "configure-verifier", verifier.uuid,
"--deployment-id", env["uuid"], "--override", tf.name])
"--env", env["uuid"], "--override", tf.name])
self.assertEqual(0, result.exit_code, result.output)
mock_override_configuration.assert_called_once_with(
verifier_id=verifier.uuid, deployment_id=env["uuid"],
@@ -260,7 +260,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
tf.flush()
result = self.invoke([
"verify", "configure-verifier", verifier.uuid,
"--deployment-id", env["uuid"], "--extend", tf.name])
"--env", env["uuid"], "--extend", tf.name])
self.assertEqual(0, result.exit_code, result.output)
mock_configure.assert_called_once_with(
verifier=verifier.uuid, deployment_id=env["uuid"],
@@ -270,7 +270,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
# a raw json/yaml value is parsed, and --show prints the config
result = self.invoke([
"verify", "configure-verifier", verifier.uuid,
"--deployment-id", env["uuid"], "--extend", "{foo: {opt: val}}",
"--env", env["uuid"], "--extend", "{foo: {opt: val}}",
"--show"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("config-body", result.output)
@@ -355,14 +355,14 @@ class VerifyCommandsTestCase(test.CLITestCase):
# --pattern and --load-list are mutually exclusive
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id", env["uuid"],
"verify", "start", verifier.uuid, "--env", env["uuid"],
"--pattern", "p", "--load-list", "/p/a/t/h"])
self.assertEqual(1, result.exit_code, result.output)
self.assertFalse(mock_start.called)
# a missing list file is reported and exits 1
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id", env["uuid"],
"verify", "start", verifier.uuid, "--env", env["uuid"],
"--load-list", "/p/a/t/h"])
self.assertEqual(1, result.exit_code, result.output)
self.assertFalse(mock_start.called)
@@ -372,7 +372,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
tf.write("test_1\ntest_2")
tf.flush()
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id",
"verify", "start", verifier.uuid, "--env",
env["uuid"], "--tag", "foo", "--load-list", tf.name])
self.assertEqual(0, result.exit_code, result.output)
mock_start.assert_called_once_with(
@@ -386,7 +386,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
with self.subTest(opt=opt):
mock_start.reset_mock()
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id",
"verify", "start", verifier.uuid, "--env",
env["uuid"], opt, "/p/a/t/h"])
self.assertEqual(1, result.exit_code, result.output)
self.assertFalse(mock_start.called)
@@ -395,7 +395,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
tf.write("test_1:\ntest_2: Reason\n")
tf.flush()
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id",
"verify", "start", verifier.uuid, "--env",
env["uuid"], opt, tf.name])
self.assertEqual(0, result.exit_code, result.output)
self.assertEqual({"test_1": None, "test_2": "Reason"},
@@ -409,7 +409,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
"tests": {"t": {"name": "t", "status": "fail",
"traceback": "boom-trace"}}}
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id", env["uuid"],
"verify", "start", verifier.uuid, "--env", env["uuid"],
"--detailed", "--no-use"])
self.assertEqual(3, result.exit_code, result.output)
self.assertIn("boom-trace", result.output)
@@ -421,7 +421,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
"totals": {**RESULTS["totals"], "unexpected_success": 1},
"tests": RESULTS["tests"]}
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id", env["uuid"]])
"verify", "start", verifier.uuid, "--env", env["uuid"]])
self.assertEqual(2, result.exit_code, result.output)
@mock.patch("rally.api._Verification.start")
@@ -432,7 +432,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
status=consts.DeployStatus.DEPLOY_INIT)
result = self.invoke([
"verify", "start", verifier.uuid, "--deployment-id", env["uuid"]])
"verify", "start", verifier.uuid, "--env", env["uuid"]])
self.assertEqual(1, result.exit_code, result.output)
@@ -453,7 +453,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
"totals": RESULTS["totals"], "tests": RESULTS["tests"]}
result = self.invoke([
"verify", "rerun", verification["uuid"], "--deployment-id",
"verify", "rerun", verification["uuid"], "--env",
env["uuid"], "--failed"])
self.assertEqual(0, result.exit_code, result.output)
@@ -468,7 +468,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
"tests": {"t": {"name": "t", "status": "fail",
"traceback": "boom-trace"}}}
result = self.invoke([
"verify", "rerun", verification["uuid"], "--deployment-id",
"verify", "rerun", verification["uuid"], "--env",
env["uuid"], "--detailed", "--no-use"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("boom-trace", result.output)
@@ -527,7 +527,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
"RRRRRRRRRRRRRR) |\n"
"| Verifier type | fake-verifier-tool (platform: tests) "
" |\n"
"| Deployment name | Some Deploy (UUID: EEEEEEEEEEEEEEEEEEEEEE"
"| Environment | Some Deploy (UUID: EEEEEEEEEEEEEEEEEEEEEE"
"EEEEEEEEEEEEEE) |\n"
"| Tests count | 1 "
" |\n"
@@ -567,8 +567,8 @@ class VerifyCommandsTestCase(test.CLITestCase):
for args, expected in (
([], "There are no verifications."),
(["--id", verifier.uuid, "--deployment-id", env["uuid"],
"--status", "bar"],
(["--id", verifier.uuid, "--env", env["uuid"],
"--status", "finished"],
"There are no verifications that meet specified criteria."),
):
with self.subTest(args=args):
@@ -629,7 +629,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
# a missing input file is reported and exits 1
result = self.invoke([
"verify", "import", verifier.uuid, "--deployment-id", env["uuid"],
"verify", "import", verifier.uuid, "--env", env["uuid"],
"--file", "/p/a/t/h"])
self.assertEqual(1, result.exit_code, result.output)
self.assertFalse(mock_import_results.called)
@@ -639,7 +639,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
tf.write("data")
tf.flush()
result = self.invoke([
"verify", "import", verifier.uuid, "--deployment-id",
"verify", "import", verifier.uuid, "--env",
env["uuid"], "--file", tf.name])
self.assertEqual(0, result.exit_code, result.output)
mock_import_results.assert_called_once_with(
@@ -650,7 +650,7 @@ class VerifyCommandsTestCase(test.CLITestCase):
tf.write("data")
tf.flush()
result = self.invoke([
"verify", "import", verifier.uuid, "--deployment-id",
"verify", "import", verifier.uuid, "--env",
env["uuid"], "--file", tf.name, "--no-use"])
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("Verification UUID", result.output)