Fix several issues with plugin discovery error messages

* `rally plugin show` should raise errors in case of not found or
  multiple match
* remove dumplication of 'in' from PluginNotFound error in case of any
  platform search
* handle Scenario plugin not found during task validation
* doesn't tag 'PluginNotFound' with "Unexpected CLI error" since it is
  clear error

Change-Id: Ibe003c26ef973b18decaa16c03b3589b4002157e
This commit is contained in:
Andrey Kurilin 2020-02-14 15:59:29 +02:00
parent 000052f67a
commit 302216c1a2
17 changed files with 79 additions and 45 deletions

View File

@ -25,6 +25,11 @@ Removed
* Python 2.7, Python 3.4 and Python 3.5 support
Changed
~~~~~~~
* *rally plugin show* command returns not-zero exit code in case of not found
or multiple match errors
[2.1.0] - 2019-11-19
--------------------

View File

@ -675,7 +675,8 @@ def run(argv, categories):
except (IOError, TypeError, ValueError,
exceptions.RallyException, jsonschema.ValidationError) as e:
if logging.is_debug():
known_errors = (exceptions.InvalidTaskConfig,)
if logging.is_debug() and not isinstance(e, known_errors):
LOG.exception("Unexpected exception in CLI")
else:
print(e)

View File

@ -18,6 +18,7 @@ from __future__ import print_function
from rally.cli import cliutils
from rally.common.plugin import plugin
from rally.common import utils
from rally import exceptions
from rally import plugins
@ -58,6 +59,7 @@ class PluginCommands(object):
)
else:
print("Plugin %s not found at any platform" % name)
return exceptions.PluginNotFound.error_code
elif len(found) == 1 or exact_match:
plugin_ = found[0] if len(found) == 1 else exact_match[0]
@ -79,6 +81,7 @@ class PluginCommands(object):
else:
print("Multiple plugins found:")
self._print_plugins_list(found)
return exceptions.MultiplePluginsFound.error_code
@cliutils.args(
"--name", dest="name", type=str,

View File

@ -560,7 +560,7 @@ class Workload(object):
task["subtasks"] = [collections.OrderedDict()]
subtask = task["subtasks"][0]
subtask["title"] = workload["name"]
subtask["description"] = workload["description"]
subtask["description"] = workload.get("description", "")
subtask["scenario"] = {workload["name"]: workload["args"]}
subtask["contexts"] = workload["contexts"]
subtask["runner"] = {workload["runner_type"]: workload["runner"]}

View File

@ -156,8 +156,10 @@ class Plugin(meta.MetaMixin, info.InfoMixin):
allow_hidden=allow_hidden)
if not results:
base = cls._get_base()
base = "" if base == Plugin else " %s" % base.__name__
raise exceptions.PluginNotFound(
name=name, platform=platform or "in any")
name=name, platform=platform or "any", base=base)
if len(results) == 1:
return results[0]

View File

@ -214,9 +214,8 @@ class ValidatablePluginMixin(object):
"""
try:
plugin = cls.get(name, allow_hidden=allow_hidden)
except exceptions.PluginNotFound:
return ["There is no %s plugin with name: '%s'" %
(cls.__name__, name)]
except exceptions.PluginNotFound as e:
return [e.format_message()]
if vtype is None:
semantic = True

View File

@ -140,7 +140,8 @@ class NotFoundException(RallyException):
class PluginNotFound(NotFoundException):
error_code = 211
msg_fmt = "There is no plugin `%(name)s` in %(platform)s platform."
msg_fmt = "There is no%(base)s plugin `%(name)s` in %(platform)s " \
"platform."
class PluginWithSuchNameExists(RallyException):

View File

@ -256,7 +256,15 @@ class TaskEngine(object):
:param vcontext: a validation context
:param vtype: a type of validation (platform, syntax or semantic)
"""
scenario_cls = scenario.Scenario.get(workload["name"])
try:
scenario_cls = scenario.Scenario.get(workload["name"])
except exceptions.PluginNotFound as e:
raise exceptions.InvalidTaskConfig(
name=workload["name"],
pos=workload["position"],
config=json.dumps(objects.Workload.to_task(workload)),
reason=e.format_message()) from None
scenario_context = copy.deepcopy(scenario_cls.get_default_context())
results = []
@ -318,14 +326,11 @@ class TaskEngine(object):
vtype=vtype))
if results:
msg = "\n ".join(results)
kw = {"name": workload["name"],
"pos": workload["position"],
"config": json.dumps(
objects.Workload.to_task(workload)),
"reason": msg}
raise exceptions.InvalidTaskConfig(**kw)
raise exceptions.InvalidTaskConfig(
name=workload["name"],
pos=workload["position"],
config=json.dumps(objects.Workload.to_task(workload)),
reason="\n ".join(results))
@logging.log_task_wrapper(LOG.info, "Task validation of syntax.")
def _validate_config_syntax(self, config):

View File

@ -51,3 +51,9 @@
shell:
cmd: |
sudo pip3 install bindep
- name: Prepare rally plugins stored at home dir
shell:
cmd: |
mkdir --parents ~/.rally/plugins
cp --recursive {{ zuul.project.src_dir }}/rally-jobs/plugins/* ~/.rally/plugins

View File

@ -13,15 +13,23 @@
become: yes
when: bindep_output.stdout_lines
- name: Install Rally and check
- name: Install Rally system wide
shell:
executable: /bin/sh
chdir: '{{ zuul.project.src_dir }}'
cmd: "sudo pip3 install --constraint ./upper-constraints.txt ./"
- name: Check Rally base commands
shell:
cmd: |
# install system wide
sudo pip3 install --constraint ./upper-constraints.txt ./
rally --version
rally db create
rally env list
# should be loaded from ~/.rally/plugins
rally plugin show --name FakePlugin.testplugin
# builtin plugin
rally plugin show --name HttpRequests.check_request
executable: /bin/sh
chdir: '{{ zuul.project.src_dir }}'

View File

@ -12,12 +12,12 @@
import os
import unittest
import testtools
from tests.functional import utils
class DeploymentTestCase(unittest.TestCase):
class DeploymentTestCase(testtools.TestCase):
def test_create_deployment_from_env(self):
os.environ.update(

View File

@ -17,12 +17,12 @@ import json
import os
import tempfile
import unittest
import testtools
from tests.functional import utils
class EnvTestCase(unittest.TestCase):
class EnvTestCase(testtools.TestCase):
def test_create_no_spec(self):
rally = utils.Rally()

View File

@ -14,14 +14,14 @@
# under the License.
import subprocess
import unittest
import six
import testtools
from rally.utils import encodeutils
class CLITestCase(unittest.TestCase):
class CLITestCase(testtools.TestCase):
def test_rally_cli(self):
try:

View File

@ -13,12 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import unittest
import testtools
from tests.functional import utils
class PluginTestCase(unittest.TestCase):
class PluginTestCase(testtools.TestCase):
def test_show_one(self):
rally = utils.Rally()
@ -30,29 +30,32 @@ class PluginTestCase(unittest.TestCase):
def test_show_multiple(self):
rally = utils.Rally()
result = rally("plugin show Dummy")
self.assertIn("Multiple plugins found:", result)
self.assertIn("Dummy.dummy", result)
self.assertIn("Dummy.dummy_exception", result)
self.assertIn("Dummy.dummy_random_fail_in_atomic", result)
result = self.assertRaises(utils.RallyCliError,
rally, "plugin show Dummy")
self.assertIn("Multiple plugins found:", result.output)
self.assertIn("Dummy.dummy", result.output)
self.assertIn("Dummy.dummy_exception", result.output)
self.assertIn("Dummy.dummy_random_fail_in_atomic", result.output)
def test_show_not_found(self):
rally = utils.Rally()
name = "Dummy666666"
result = rally("plugin show %s" % name)
self.assertIn("Plugin %s not found" % name, result)
result = self.assertRaises(utils.RallyCliError,
rally, "plugin show %s" % name)
self.assertIn("Plugin %s not found" % name, result.output)
def test_show_not_found_in_specific_platform(self):
rally = utils.Rally()
name = "Dummy"
platform = "non_existing"
result = rally(
"plugin show --name %(name)s --platform %(platform)s"
% {"name": name, "platform": platform})
result = self.assertRaises(
utils.RallyCliError,
rally, "plugin show --name %(name)s --platform %(platform)s"
% {"name": name, "platform": platform})
self.assertIn(
"Plugin %(name)s@%(platform)s not found"
% {"name": name, "platform": platform},
result)
result.output)
def test_list(self):
rally = utils.Rally()

View File

@ -15,12 +15,13 @@
import os
import subprocess
import unittest
import testtools
from tests.functional import utils
class LibAPITestCase(unittest.TestCase):
class LibAPITestCase(testtools.TestCase):
def test_rally_lib(self):
rally = utils.Rally(force_new_db=True)

View File

@ -203,7 +203,7 @@ class Rally(object):
except subprocess.CalledProcessError as e:
output = encodeutils.safe_decode(e.output)
raise RallyCliError(cmd, e.returncode, output)
raise RallyCliError(cmd, e.returncode, output) from None
finally:
if write_report:
if not report_path:

View File

@ -98,8 +98,8 @@ class ValidatorTestCase(test.TestCase):
result = DummyPluginBase.validate("dummy_plugin", None, None, None)
self.assertEqual(1, len(result))
self.assertIn("There is no DummyPluginBase plugin "
"with name: 'dummy_plugin'", result[0])
self.assertIn("There is no DummyPluginBase plugin `dummy_plugin`",
result[0])
def test_failure_includes_detailed_info(self):