[envs] Implmenet Env CLI commands

- Introduce Env CLI commands & bash completition
- Use RALLY_ENV variable in both cases for deployments and env
- Smooth RALLY_DEPLOYMENT -> RALLY_ENV migration
- Implement unit tests
- Implement functional tests

Change-Id: I56a60bd35bdc5ff833fdcad19f13ecf55496a316
This commit is contained in:
Boris Pavlovic 2018-01-22 00:45:38 -08:00
parent eeb1de65f7
commit e7d6155298
12 changed files with 853 additions and 31 deletions

View File

@ -32,6 +32,14 @@ _rally()
OPTS["deployment_recreate"]="--filename --deployment"
OPTS["deployment_show"]="--deployment"
OPTS["deployment_use"]="--deployment"
OPTS["env_check"]="--env --json --detailed"
OPTS["env_create"]="--name --description --extras --spec --json --no-use"
OPTS["env_delete"]="--env --force"
OPTS["env_destroy"]="--env --skip-cleanup --json --detailed"
OPTS["env_info"]="--env --json"
OPTS["env_list"]="--json"
OPTS["env_show"]="--env --json"
OPTS["env_use"]="--env --json"
OPTS["plugin_list"]="--name --platform --plugin-base"
OPTS["plugin_show"]="--name --platform"
OPTS["task_abort"]="--uuid --soft"
@ -97,4 +105,4 @@ _rally()
return 0
}
complete -o filenames -F _rally rally
complete -o filenames -F _rally rally

View File

@ -309,7 +309,9 @@ class DeploymentCommands(object):
return 1
print("Using deployment: %s" % deployment["uuid"])
fileutils.update_globals_file("RALLY_DEPLOYMENT",
fileutils.update_globals_file(envutils.ENV_DEPLOYMENT,
deployment["uuid"])
fileutils.update_globals_file(envutils.ENV_ENV,
deployment["uuid"])
if "openstack" in deployment["credentials"]:

269
rally/cli/commands/env.py Normal file
View File

@ -0,0 +1,269 @@
# All Rights Reserved.
#
# 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.
from __future__ import print_function
import json
import os
import prettytable
import traceback
from rally.cli import cliutils
from rally.cli import envutils
from rally.common import fileutils
from rally.common import yamlutils as yaml
from rally.env import env_mgr
from rally import exceptions
YES = u":-)"
NO = u":-("
def _print(msg, silent=False):
if not silent:
print(msg)
# TODO(boris-42): Wrap all methods to catch EnvManager Exceptions
class EnvCommands(object):
"""Set of commands that allow you to manage envs."""
@cliutils.args("--name", "-n", type=str, required=True,
help="Name of the env.")
@cliutils.args("--description", "-d", type=str, required=False,
help="Env description")
@cliutils.args("--extras", "-e", type=str, required=False,
help="JSON or YAML dict with custom non validate info.")
@cliutils.args("--spec", "-s", type=str, required=False,
metavar="<path>", help="Path to env spec.")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
@cliutils.args("--no-use", action="store_false", dest="do_use",
help="Don't set new env as default for future operations.")
def create(self, api, name, description=None,
extras=None, spec=None, to_json=False, do_use=True):
"""Create new environment."""
spec = spec or {}
if spec:
with open(os.path.expanduser(spec), "rb") as f:
spec = yaml.safe_load(f.read())
if extras:
extras = yaml.safe_load(extras)
try:
env = env_mgr.EnvManager.create(
name, spec, description=description, extras=extras)
except exceptions.ManagerInvalidSpec as e:
_print("Env spec has wrong format:", to_json)
_print(json.dumps(e.kwargs["spec"], indent=2), to_json)
for err in e.kwargs["errors"]:
_print(err, to_json)
return 1
except Exception:
_print("Something went wrong during env creation:", to_json)
_print(traceback.print_exc(), to_json)
return 1
if do_use:
self._use(env.uuid, to_json)
self._show(env.data, to_json)
return 0
@cliutils.args("--env", dest="env", type=str,
metavar="<uuid>", required=False,
help="UUID or name of the env.")
@cliutils.args("--skip-cleanup", action="store_true", dest="skip_cleanup",
help="Do not perform platforms cleanup before destroy.")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
@cliutils.args("--detailed", action="store_true", dest="detailed",
help="Show detailed information.")
@envutils.with_default_env()
def destroy(self, api, env=None, skip_cleanup=False, to_json=False,
detailed=False):
"""Destroy existing environment."""
env = env_mgr.EnvManager.get(env)
_print("Destroying %s" % env, to_json)
result = env.destroy(skip_cleanup)
return_code = int(result["destroy_info"]["skipped"])
if result["destroy_info"]["skipped"]:
_print("%s Failed to destroy env %s: %s"
% (NO, env, result["destroy_info"]["message"]), to_json)
else:
_print("%s Successfully destroyed env %s" % (YES, env), to_json)
if detailed or to_json:
print(json.dumps(result, indent=2))
return return_code
@cliutils.args("--env", dest="env", type=str,
metavar="<uuid>", required=False,
help="UUID or name of the env.")
@cliutils.args("--force", action="store_true", dest="force",
help="Delete DB records even if env is not destroyed.")
@envutils.with_default_env()
def delete(self, api, env=None, force=False):
"""Deletes all records related to Env from db."""
env_mgr.EnvManager.get(env).delete(force=force)
# TODO(boris-42): clear env variables if default one is deleted
MSG_NO_ENVS = ("There are no environments. To create a new environment, "
"use command bellow to create one:\nrally env create")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
@cliutils.suppress_warnings
def list(self, api, to_json=False):
"""List existing environments."""
envs = env_mgr.EnvManager.list()
if to_json:
print(json.dumps([env.cached_data for env in envs], indent=2))
elif not envs:
print(self.MSG_NO_ENVS)
else:
cur_env = envutils.get_global(envutils.ENV_ENV)
table = prettytable.PrettyTable()
fields = ["uuid", "name", "status", "created_at", "description"]
table.field_names = fields + ["default"]
for env in envs:
row = [env.cached_data[f] for f in fields]
row.append(cur_env == env.cached_data["uuid"] and "*" or "")
table.add_row(row)
table.sortby = "created_at"
table.reversesort = True
table.align = "l"
print(table.get_string())
def _show(self, env_data, to_json):
if to_json:
print(json.dumps(env_data, indent=2))
else:
table = prettytable.PrettyTable()
table.header = False
for k in ["uuid", "name", "status",
"created_at", "updated_at", "description"]:
table.add_row([k, env_data[k]])
table.add_row(["extras", json.dumps(env_data["extras"], indent=2)])
for p, data in env_data["platforms"].items():
table.add_row(["platform: %s" % p,
json.dumps(data["platform_data"], indent=2)])
table.align = "l"
print(table.get_string())
@cliutils.args("--env", dest="env", type=str,
metavar="<uuid>", required=False,
help="UUID or name of the env.")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
@cliutils.suppress_warnings
@envutils.with_default_env()
def show(self, api, env=None, to_json=False):
env_data = env_mgr.EnvManager.get(env).data
self._show(env_data, to_json)
@cliutils.args("--env", dest="env", type=str,
metavar="<uuid>", required=False,
help="UUID or name of the env.")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
@envutils.with_default_env()
def info(self, api, env=None, to_json=False):
"""Show environment information."""
env = env_mgr.EnvManager.get(env)
env_info = env.get_info()
return_code = int(any(v.get("error") for v in env_info.values()))
if to_json:
print(json.dumps(env_info, indent=2))
return return_code
table = prettytable.PrettyTable()
table.field_names = ["platform", "info", "error"]
for platform, data in env_info.items():
table.add_row([
platform, json.dumps(data["info"], indent=2),
data.get("error") or ""
])
table.align = "l"
print(env)
print(table.get_string())
return return_code
@cliutils.args("--env", dest="env", type=str,
metavar="<uuid>", required=False,
help="UUID or name of the env.")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
@cliutils.args("--detailed", action="store_true", dest="detailed",
help="Show detailed information.")
@envutils.with_default_env()
def check(self, api, env=None, to_json=False, detailed=False):
"""Check availability of all platforms in environment."""
env = env_mgr.EnvManager.get(env)
data = env.check_health()
available = all(x["available"] for x in data.values())
if to_json:
print(json.dumps(data, indent=2))
return not available
def _format_raw(plugin_name, el):
return [
el["available"] and YES or NO,
plugin_name.split("@")[1], el["message"], plugin_name
]
table = prettytable.PrettyTable()
if detailed:
table.field_names = ["Available", "Platform", "Message", "Plugin"]
for plugin_name, r in data.items():
table.add_row(_format_raw(plugin_name, r))
else:
table.field_names = ["Available", "Platform", "Message"]
for plugin_name, r in data.items():
table.add_row(_format_raw(plugin_name, r)[:3])
table.align = "l"
table.align["available"] = "c"
print("%s %s" % (env, available and YES or NO))
print(table.get_string())
if not available and detailed:
for name, p_data in data.items():
if p_data["available"]:
continue
print("-" * 4)
print("Plugin %s raised exception:" % name)
print("".join(p_data["traceback"]))
return not available
@cliutils.args("--env", dest="env", type=str,
metavar="<uuid>", required=False,
help="UUID or name of a env.")
@cliutils.args("--json", action="store_true", dest="to_json",
help="Format output as JSON.")
def use(self, api, env, to_json=False):
"""Set default environment."""
try:
env = env_mgr.EnvManager.get(env)
except exceptions.DBRecordNotFound:
_print("Can't use non existing environment %s." % env, to_json)
return 1
self._use(env.uuid, to_json)
def _use(self, env_uuid, to_json):
_print("Using environment: %s" % env_uuid, to_json)
fileutils.update_globals_file(envutils.ENV_ENV, env_uuid)

View File

@ -22,11 +22,12 @@ from rally.common import fileutils
from rally import exceptions
PATH_GLOBALS = "~/.rally/globals"
ENV_ENV = "RALLY_ENV"
ENV_DEPLOYMENT = "RALLY_DEPLOYMENT"
ENV_TASK = "RALLY_TASK"
ENV_VERIFIER = "RALLY_VERIFIER"
ENV_VERIFICATION = "RALLY_VERIFICATION"
ENVVARS = [ENV_DEPLOYMENT, ENV_TASK, ENV_VERIFIER, ENV_VERIFICATION]
ENVVARS = [ENV_ENV, ENV_DEPLOYMENT, ENV_TASK, ENV_VERIFIER, ENV_VERIFICATION]
MSG_MISSING_ARG = "Missing argument: --%(arg_name)s"
@ -69,9 +70,31 @@ def default_from_global(arg_name, env_name,
return decorator.decorator(default_from_global)
def with_default_deployment(cli_arg_name="uuid"):
def with_default_env():
# NOTE(boris-42): This allows smooth transition from deployment to env
# set ENV_ENV from ENV_DEPLOYMENT if ENV is not presented
# This should be removed with rally env command
if not os.environ.get(ENV_ENV):
if os.environ.get(ENV_DEPLOYMENT):
os.environ[ENV_ENV] = ENV_DEPLOYMENT
return default_from_global(
"deployment", ENV_DEPLOYMENT, cli_arg_name,
"env", ENV_ENV, "env",
message="There is no default env. To set it use command:\n"
"\trally env use <env_uuid>|<env_name>\n"
"or pass uuid or name to your command using --%(arg_name)s")
def with_default_deployment(cli_arg_name="uuid"):
# NOTE(boris-42): This allows smooth transition from deployment to env
# set ENV_ENV from ENV_DEPLOYMENT and use ENV_ENV
# This should be removed with rally env command
if not os.environ.get(ENV_ENV):
if os.environ.get(ENV_DEPLOYMENT):
os.environ[ENV_ENV] = ENV_DEPLOYMENT
return default_from_global(
"deployment", ENV_ENV, cli_arg_name,
message="There is no default deployment.\n"
"\tPlease use command:\n"
"\trally deployment use <deployment_uuid>|<deployment_name>\n"

View File

@ -22,6 +22,7 @@ import sys
from rally.cli import cliutils
from rally.cli.commands import db
from rally.cli.commands import deployment
from rally.cli.commands import env
from rally.cli.commands import plugin
from rally.cli.commands import task
from rally.cli.commands import verify
@ -29,6 +30,7 @@ from rally.cli.commands import verify
categories = {
"db": db.DBCommands,
"env": env.EnvCommands,
"deployment": deployment.DeploymentCommands,
"plugin": plugin.PluginCommands,
"task": task.TaskCommands,
@ -39,5 +41,6 @@ categories = {
def main():
return cliutils.run(sys.argv, categories)
if __name__ == "__main__":
sys.exit(main())

18
rally/env/env_mgr.py vendored
View File

@ -359,12 +359,15 @@ class EnvManager(object):
platform plugins and their arguments.
:returns: EnvManager instance corresponding to created Env
"""
if description is not None:
spec["!description"] = description
if extras is not None:
spec["!extras"] = extras
if config is not None:
spec["!config"] = config
# NOTE(boris-42): this allows to avoid validation copy paste. If spec
# is not dict it will fail during validation process
if isinstance(spec, dict):
if description is not None:
spec["!description"] = description
if extras is not None:
spec["!extras"] = extras
if config is not None:
spec["!config"] = config
self = cls._validate_and_create_env(name, spec)
self._create_platforms()
@ -613,6 +616,9 @@ class EnvManager(object):
from rally.common import objects
# TODO(boris-42): This is breaking all kinds of rules of good
# architecture, and we should remove this thing from
# here...
for verifier in objects.Verifier.list():
verifier.set_env(self.uuid)
verifier.manager.uninstall()

View File

@ -94,11 +94,6 @@ class ManagerInvalidSpec(ManagerException):
error_code = 409
msg_fmt = "%(mgr)s manager got invalid spec: \n%(errors)s"
def __init__(self, **kwargs):
kwargs["errors"] = "\n".join(kwargs["errors"])
self.spec = kwargs.pop("spec", "")
super(ManagerInvalidSpec, self).__init__(**kwargs)
class ManagerInvalidState(ManagerException):
error_code = 500

View File

@ -0,0 +1,43 @@
# All Rights Reserved.
#
# 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.
from rally.env import platform
@platform.configure(name="good", platform="fake")
class GoodPlatform(platform.Platform):
CONFIG_SCHEMA = {}
def create(self):
return {}, {}
def destroy(self):
pass
def cleanup(self, task_uuid=None):
return {
"message": "Coming soon!",
"discovered": 0,
"deleted": 0,
"failed": 0,
"resources": {},
"errors": []
}
def check_health(self):
return {"available": True}
def info(self):
return {"info": {"a": 1}}

View File

@ -0,0 +1,92 @@
# Copyright 2013: ITLook, Inc.
# All Rights Reserved.
#
# 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 json
import os
import tempfile
import unittest
from tests.functional import utils
class EnvTestCase(unittest.TestCase):
def test_create_no_spec(self):
rally = utils.Rally()
rally("env create --name empty --description de")
self.assertIn("empty", rally("env list"))
env_data = rally("env show --json", getjson=True)
self.assertEqual("empty", env_data["name"])
self.assertEqual("de", env_data["description"])
self.assertEqual({}, env_data["extras"])
self.assertEqual({}, env_data["platforms"])
def _create_spec(self, spec):
f = tempfile.NamedTemporaryFile(delete=False)
def unlink():
os.unlink(f.name)
self.addCleanup(unlink)
f.write(json.dumps(spec, indent=2))
f.close()
return f.name
def test_create_check_info_destroy_delete_with_spec(self):
rally = utils.Rally(plugin_path="tests/functional/extra")
spec = self._create_spec({"good@fake": {}})
rally("env create --name real --spec %s" % spec)
env = rally("env show --json", getjson=True)
self.assertIn("fake", env["platforms"])
env_info = rally("env info --json", getjson=True)
self.assertEqual({"good@fake": {"info": {"a": 1}}}, env_info)
rally("env check --json")
def test_list_empty(self):
rally = utils.Rally()
# TODO(boris-42): Clean this up somehow
rally("env destroy MAIN")
rally("env delete MAIN")
self.assertEqual([], rally("env list --json", getjson=True))
self.assertIn("There are no environments", rally("env list"))
def test_list(self):
rally = utils.Rally()
envs = rally("env list --json", getjson=True)
self.assertEqual(1, len(envs))
self.assertEqual("MAIN", envs[0]["name"])
self.assertIn("MAIN", rally("env list"))
def test_use(self):
def show_helper():
return rally("env show --json", getjson=True)
rally = utils.Rally()
self.assertEqual("MAIN", show_helper()["name"])
empty_uuid = rally("env create --name empty --json",
getjson=True)["uuid"]
self.assertEqual("empty", show_helper()["name"])
rally("env use MAIN")
self.assertEqual("MAIN", show_helper()["name"])
rally("env use %s" % empty_uuid)
self.assertEqual("empty", show_helper()["name"])
rally("env create --name empty2 --description de --no-use")
self.assertEqual("empty", show_helper()["name"])

View File

@ -186,16 +186,13 @@ class Rally(object):
if not isinstance(cmd, list):
cmd = cmd.split(" ")
try:
if getjson:
if no_logs or getjson:
cmd = self.args + ["--log-file", "/dev/null"] + cmd
else:
cmd = self.args + cmd
if no_logs:
with open(os.devnull, "w") as DEVNULL:
output = encodeutils.safe_decode(subprocess.check_output(
cmd, stderr=DEVNULL, env=self.env))
else:
cmd = self.args + cmd
output = encodeutils.safe_decode(subprocess.check_output(
cmd, stderr=subprocess.STDOUT, env=self.env))

View File

@ -298,10 +298,14 @@ class DeploymentCommandsTestCase(test.TestCase):
with mock.patch("rally.cli.commands.deployment.open", mock.mock_open(),
create=True) as mock_file:
self.deployment.use(self.fake_api, deployment_id)
self.assertEqual(2, mock_path_exists.call_count)
mock_update_env_file.assert_called_once_with(os.path.expanduser(
"~/.rally/globals"),
"RALLY_DEPLOYMENT", "%s\n" % deployment_id)
self.assertEqual(3, mock_path_exists.call_count)
mock_update_env_file.assert_has_calls([
mock.call(os.path.expanduser("~/.rally/globals"),
"RALLY_DEPLOYMENT", "%s\n" % deployment_id),
mock.call(os.path.expanduser("~/.rally/globals"),
"RALLY_ENV", "%s\n" % deployment_id),
])
mock_file.return_value.write.assert_any_call(
"export OS_ENDPOINT='fake_endpoint'\n")
mock_file.return_value.write.assert_any_call(
@ -340,10 +344,13 @@ class DeploymentCommandsTestCase(test.TestCase):
with mock.patch("rally.cli.commands.deployment.open", mock.mock_open(),
create=True) as mock_file:
self.deployment.use(self.fake_api, deployment_id)
self.assertEqual(2, mock_path_exists.call_count)
mock_update_env_file.assert_called_once_with(os.path.expanduser(
"~/.rally/globals"),
"RALLY_DEPLOYMENT", "%s\n" % deployment_id)
self.assertEqual(3, mock_path_exists.call_count)
mock_update_env_file.assert_has_calls([
mock.call(os.path.expanduser("~/.rally/globals"),
"RALLY_DEPLOYMENT", "%s\n" % deployment_id),
mock.call(os.path.expanduser("~/.rally/globals"),
"RALLY_ENV", "%s\n" % deployment_id)
])
mock_file.return_value.write.assert_any_call(
"export OS_ENDPOINT='fake_endpoint'\n")
mock_file.return_value.write.assert_any_call(
@ -376,8 +383,10 @@ class DeploymentCommandsTestCase(test.TestCase):
self.assertIsNone(status)
self.fake_api.deployment.get.assert_called_once_with(
deployment="fake_name")
mock_update_globals_file.assert_called_once_with(
envutils.ENV_DEPLOYMENT, "fake_uuid")
mock_update_globals_file.assert_has_calls([
mock.call(envutils.ENV_DEPLOYMENT, "fake_uuid"),
mock.call(envutils.ENV_ENV, "fake_uuid")
])
mock__update_openrc_deployment_file.assert_called_once_with(
"fake_uuid", "foo_admin")

View File

@ -0,0 +1,375 @@
# Copyright 2018: ITLook Inc.
# All Rights Reserved.
#
# 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 datetime as dt
import json
import uuid
import mock
from rally.cli.commands import env
from rally.env import env_mgr
from rally import exceptions
from tests.unit import test
class EnvCommandsTestCase(test.TestCase):
def setUp(self):
super(EnvCommandsTestCase, self).setUp()
self.env = env.EnvCommands()
# TODO(boris-42): api argument is not used by EvnCommands
# it's going to be removed when we remove rally.api
# from other commands
self.api = None
@staticmethod
def gen_env_data(uid=None, name=None, description=None,
status=env_mgr.STATUS.INIT, spec=None, extras=None):
return {
"uuid": uid or str(uuid.uuid4()),
"created_at": dt.datetime(2017, 1, 1),
"updated_at": dt.datetime(2017, 1, 2),
"name": name or str(uuid.uuid4()),
"description": description or str(uuid.uuid4()),
"status": status,
"spec": spec or {},
"extras": extras or {}
}
@mock.patch("rally.cli.commands.env.print")
def test__print(self, mock_print):
env._print("Test42", silent=True)
self.assertFalse(mock_print.called)
env._print("Test42", silent=False)
mock_print.assert_called_once_with("Test42")
env._print("Test43")
mock_print.assert_has_calls([mock.call("Test42"), mock.call("Test43")])
@mock.patch("rally.env.env_mgr.EnvManager.create")
@mock.patch("rally.cli.commands.env.EnvCommands._show")
def test_create_emtpy_use(self, mock_env_commands__show,
mock_env_manager_create):
self.assertEqual(
0, self.env.create(self.api, "test_name", "test_description"))
mock_env_manager_create.assert_called_once_with(
"test_name", {}, description="test_description", extras=None)
mock_env_commands__show.assert_called_once_with(
mock_env_manager_create.return_value.data, False)
@mock.patch("rally.env.env_mgr.EnvManager.create")
@mock.patch("rally.cli.commands.env.open", create=True)
@mock.patch("rally.cli.commands.env.print")
def test_create_spec_and_extra_no_use_to_json(self, mock_print, mock_open,
mock_env_manager_create):
mock_open.side_effect = mock.mock_open(read_data="{\"a\": 1}")
mock_env_manager_create.return_value.data = {"test": "test"}
self.assertEqual(
0, self.env.create(self.api, "n", "d", extras="{\"extra\": 123}",
spec="spec.yml", to_json=True, do_use=False))
mock_env_manager_create.assert_called_once_with(
"n", {"a": 1}, description="d", extras={"extra": 123})
mock_print.assert_called_once_with(
json.dumps(mock_env_manager_create.return_value.data, indent=2))
@mock.patch("rally.cli.commands.env.print")
@mock.patch("rally.cli.commands.env.open", create=True)
def test_create_invalid_spec(self, mock_open, mock_print):
mock_open.side_effect = mock.mock_open(read_data="[]")
self.assertEqual(
1, self.env.create(self.api, "n", "d", spec="spec.yml"))
mock_print.assert_has_calls([
mock.call("Env spec has wrong format:"),
mock.call("[]"),
mock.call(mock.ANY)
])
@mock.patch("rally.env.env_mgr.EnvManager.create")
@mock.patch("rally.cli.commands.env.print")
def test_create_exception(self, mock_print, mock_env_manager_create):
mock_env_manager_create.side_effect = Exception
self.assertEqual(1, self.env.create(self.api, "n", "d"))
mock_print.assert_has_calls([
mock.call("Something went wrong during env creation:"),
mock.call(mock.ANY)
])
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_destroy(self, mock_print, mock_env_manager_get):
env_ = mock.Mock()
env_inst = mock_env_manager_get.return_value
env_inst.destroy.return_value = {
"destroy_info": {
"skipped": True,
"message": "42"
}
}
self.assertEqual(1, self.env.destroy(self.api, env_))
mock_env_manager_get.assert_called_once_with(env_)
env_inst.destroy.assert_called_once_with(False)
mock_print.assert_has_calls([
mock.call("Destroying %s" % env_inst),
mock.call(":-( Failed to destroy env %s: 42" % env_inst)
])
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_destroy_to_json(self, mock_print, mock_env_manager_get):
env_ = mock.Mock()
env_inst = mock_env_manager_get.return_value
env_inst.destroy.return_value = {
"cleanup_info": {
"skipped": False
},
"destroy_info": {
"skipped": False,
"message": "42"
}
}
self.assertEqual(
0,
self.env.destroy(self.api, env_, skip_cleanup=True, to_json=True))
env_inst.destroy.assert_called_once_with(True)
mock_print.assert_called_once_with(
json.dumps(env_inst.destroy.return_value, indent=2))
@mock.patch("rally.env.env_mgr.EnvManager.get")
def test_delete(self, mock_env_manager_get):
env_ = mock.Mock()
self.env.delete(self.api, env_)
mock_env_manager_get.assert_called_once_with(env_)
mock_env_manager_get.return_value.delete.assert_called_once_with(
force=False)
@mock.patch("rally.env.env_mgr.EnvManager.get")
def test_delete_force(self, mock_env_manager_get):
env_ = mock.Mock()
self.env.delete(self.api, env_, force=True)
mock_env_manager_get.assert_called_once_with(env_)
mock_env_manager_get.return_value.delete.assert_called_once_with(
force=True)
@mock.patch("rally.env.env_mgr.EnvManager.list")
@mock.patch("rally.cli.commands.env.print")
def test_list_empty(self, mock_print, mock_env_manager_list):
mock_env_manager_list.return_value = []
self.env.list(self.api, to_json=True)
mock_print.assert_called_once_with("[]")
mock_print.reset_mock()
self.env.list(self.api, to_json=False)
mock_print.assert_called_once_with(self.env.MSG_NO_ENVS)
@mock.patch("rally.env.env_mgr.EnvManager.list")
@mock.patch("rally.cli.commands.env.print")
def test_list(self, mock_print, mock_env_manager_list):
env_a = env_mgr.EnvManager(self.gen_env_data())
env_b = env_mgr.EnvManager(self.gen_env_data())
mock_env_manager_list.return_value = [env_a, env_b]
self.env.list(self.api, to_json=True)
mock_env_manager_list.assert_called_once_with()
mock_print.assert_called_once_with(
json.dumps([env_a.cached_data, env_b.cached_data], indent=2))
for m in [mock_env_manager_list, mock_print]:
m.reset_mock()
self.env.list(self.api)
mock_env_manager_list.assert_called_once_with()
mock_print.assert_called_once_with(mock.ANY)
@mock.patch("rally.cli.commands.env.print")
def test__show(self, mock_print):
env_data = self.gen_env_data(
uid="a77004a6-7fe5-4b75-a278-009c3c5f6b20",
name="my best env",
description="description")
env_data["platforms"] = {}
self.env._show(env_data, False)
mock_print.assert_called_once_with(
"+-------------+--------------------------------------+\n"
"| uuid | a77004a6-7fe5-4b75-a278-009c3c5f6b20 |\n"
"| name | my best env |\n"
"| status | INITIALIZING |\n"
"| created_at | 2017-01-01 00:00:00 |\n"
"| updated_at | 2017-01-02 00:00:00 |\n"
"| description | description |\n"
"| extras | {} |\n"
"+-------------+--------------------------------------+")
@mock.patch("rally.cli.commands.env.print")
def test__show_to_json(self, mock_print):
self.env._show("data", True)
mock_print.assert_called_once_with("\"data\"")
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.EnvCommands._show")
def test_show(self, mock__show, mock_env_manager_get):
env_ = mock.Mock()
self.env.show(self.api, env_)
mock_env_manager_get.assert_called_once_with(env_)
mock__show.assert_called_once_with(
mock_env_manager_get.return_value.data, False)
mock__show.reset_mock()
self.env.show(self.api, env_, to_json=True)
mock__show.assert_called_once_with(
mock_env_manager_get.return_value.data, True)
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_info_to_json(self, mock_print, mock_env_manager_get):
mock_env_manager_get.return_value.get_info.return_value = {
"p1": {"info": {"a": True}}}
self.assertEqual(0, self.env.info(self.api, "any", to_json=True))
mock_env_manager_get.assert_called_once_with("any")
mock_print.assert_called_once_with(
json.dumps(mock_env_manager_get.return_value.get_info.return_value,
indent=2)
)
mock_env_manager_get.return_value.get_info.return_value = {
"p1": {"info": {"a": False}},
"p2": {"info": {}, "error": "some error"}
}
self.assertEqual(1, self.env.info(self.api, "any", to_json=True))
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_info(self, mock_print, mock_env_manager_get):
mock_env_manager_get.return_value.get_info.return_value = {
"p1@pl1": {"info": {"a": False}},
"p2@pl2": {"info": {}, "error": "some error"}
}
self.assertEqual(1, self.env.info(self.api, "any"))
mock_print.assert_has_calls([
mock.call(mock_env_manager_get.return_value),
mock.call(
"+----------+--------------+------------+\n"
"| platform | info | error |\n"
"+----------+--------------+------------+\n"
"| p1@pl1 | { | |\n"
"| | \"a\": false | |\n"
"| | } | |\n"
"| p2@pl2 | {} | some error |\n"
"+----------+--------------+------------+"
)
])
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_check(self, mock_print, mock_env_manager_get):
mock_env_manager_get.return_value.check_health.return_value = {
"p1@p1": {"available": True, "message": "OK!"},
"p2@p2": {"available": False, "message": "BAD !"}
}
self.assertEqual(1, self.env.check(self.api, "env_42"))
mock_env_manager_get.assert_called_once_with("env_42")
mock_print.assert_has_calls([
mock.call("%s :-(" % mock_env_manager_get.return_value),
mock.call(
"+-----------+----------+---------+\n"
"| Available | Platform | Message |\n"
"+-----------+----------+---------+\n"
"| :-( | p2 | BAD ! |\n"
"| :-) | p1 | OK! |\n"
"+-----------+----------+---------+"
)
])
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_check_detailed(self, mock_print, mock_env_manager_get):
mock_env_manager_get.return_value.check_health.return_value = {
"p1@p1": {"available": True, "message": "OK!"},
"p2@p2": {"available": False, "message": "BAD !",
"traceback": "Filaneme\n Codeline\nError"}
}
self.assertEqual(1, self.env.check(self.api, "env_42", detailed=True))
mock_env_manager_get.assert_called_once_with("env_42")
print(mock_print.call_args_list)
mock_print.assert_has_calls([
mock.call("%s :-(" % mock_env_manager_get.return_value),
mock.call(
"+-----------+----------+---------+--------+\n"
"| Available | Platform | Message | Plugin |\n"
"+-----------+----------+---------+--------+\n"
"| :-( | p2 | BAD ! | p2@p2 |\n"
"| :-) | p1 | OK! | p1@p1 |\n"
"+-----------+----------+---------+--------+"
),
mock.call("----"),
mock.call("Plugin p2@p2 raised exception:"),
mock.call("Filaneme\n Codeline\nError")
])
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.print")
def test_check_to_json(self, mock_print, mock_env_manager_get):
mock_env_manager_get.return_value.check_health.return_value = {
"p1": {"available": True}}
self.assertEqual(0, self.env.check(self.api, "some_env", to_json=True))
mock_env_manager_get.assert_called_once_with("some_env")
mock_print.assert_called_once_with(
json.dumps(
mock_env_manager_get.return_value.check_health.return_value,
indent=2)
)
mock_env_manager_get.return_value.check_health.return_value = {
"p1": {"available": False}}
self.assertEqual(1, self.env.check(self.api, "some_env", to_json=True))
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env._print")
def test_use_not_found(self, mock__print, mock_env_manager_get):
mock_env_manager_get.side_effect = exceptions.DBRecordNotFound(
criteria="", table="")
env_ = str(uuid.uuid4())
self.assertEqual(1, self.env.use(self.api, env_))
mock_env_manager_get.assert_called_once_with(env_)
mock__print.assert_called_once_with(
"Can't use non existing environment %s." % env_, False)
@mock.patch("rally.env.env_mgr.EnvManager.get")
@mock.patch("rally.cli.commands.env.EnvCommands._use")
def test_use(self, mock__use, mock_env_manager_get):
mock_env_manager_get.side_effect = [
mock.Mock(uuid="aa"), mock.Mock(uuid="bb")
]
self.assertIsNone(self.env.use(self.api, "aa"))
self.assertIsNone(self.env.use(self.api, "bb", to_json=True))
mock_env_manager_get.assert_has_calls(
[mock.call("aa"), mock.call("bb")])
mock__use.assert_has_calls(
[mock.call("aa", False), mock.call("bb", True)])
@mock.patch("rally.cli.commands.env.fileutils.update_globals_file")
@mock.patch("rally.cli.commands.env.print")
def test__use(self, mock_print, mock_update_globals_file):
self.env._use("aa", True)
self.assertFalse(mock_print.called)
mock_update_globals_file.assert_called_once_with("RALLY_ENV", "aa")
mock_update_globals_file.reset_mock()
self.env._use("bb", False)
mock_print.assert_called_once_with("Using environment: bb")
mock_update_globals_file.assert_called_once_with("RALLY_ENV", "bb")