From 8206d1b1b260d2c01716e1e5bfe23b242c9e7739 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Thu, 7 Dec 2023 14:43:05 +0100 Subject: [PATCH] Improve required_context validator to support platforms Change-Id: I05d773d710d3739742338e170511b8a5fc6f1f5e --- CHANGELOG.rst | 13 ++++++ rally/plugins/common/validators.py | 47 +++++++++++++++----- tests/unit/plugins/common/test_validators.py | 41 ++++++++++++++--- 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 67a65832f3..9de25d20e1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,6 +31,19 @@ Added * CI checks for Python 3.11 compatibility +* Support for specifying platform of context as a part of required_context + validator like bellow: + + .. code-block:: python + + from rally.task import scenario + from rally.task import validation + + @scenario.configure(name="Dummy.scenario") + @validation.add("required_context", contexts=["ctx_name@platform"]) + class ElasticsearchLogInstanceName(scenario.Scenario): + def run(self): + pass Removed ~~~~~~~ diff --git a/rally/plugins/common/validators.py b/rally/plugins/common/validators.py index f8baea3734..270b11a6d6 100644 --- a/rally/plugins/common/validators.py +++ b/rally/plugins/common/validators.py @@ -19,6 +19,8 @@ import jsonschema from rally.common import logging from rally.common import validation +from rally import exceptions +from rally.task import context as context_lib LOG = logging.getLogger(__name__) @@ -308,7 +310,7 @@ class RestrictedParametersValidator(validation.Validator): @validation.configure(name="required_contexts") class RequiredContextsValidator(validation.Validator): - def __init__(self, contexts, *args): + def __init__(self, *args, contexts=None): """Validator checks if required contexts are specified. :param contexts: list of strings and tuples with context names that @@ -323,25 +325,48 @@ class RequiredContextsValidator(validation.Validator): if args: LOG.warning("Positional argument is not what " "'required_context' decorator expects. " - "Use `contexts` argument instead") + "Use only `contexts` argument instead") else: - # it is old way validator - self.contexts = [contexts] + # it is an old way validator + self.contexts = [] + if contexts: + self.contexts.append(contexts) self.contexts.extend(args) + @staticmethod + def _match(requested_ctx_name, input_contexts): + requested_ctx_name_extended = f"{requested_ctx_name}@" + for input_ctx_name in input_contexts: + if (requested_ctx_name == input_ctx_name + or input_ctx_name.startswith(requested_ctx_name_extended)): + return True + + if "@" in requested_ctx_name: + platform_aware_name, platform = requested_ctx_name.split("@") + if platform_aware_name in input_contexts: + try: + ctx_cls = context_lib.Context.get(requested_ctx_name) + except (exceptions.PluginNotFound, + exceptions.MultiplePluginsFound): + return False + return ctx_cls.get_platform() == platform + + return False + def validate(self, context, config, plugin_cls, plugin_cfg): missing_contexts = [] - input_context = config.get("contexts", {}) + input_contexts = config.get("contexts", {}) - for name in self.contexts: - if isinstance(name, tuple): - if not set(name) & set(input_context): + for required_ctx in self.contexts: + if isinstance(required_ctx, tuple): + if not any(self._match(r_ctx, input_contexts) + for r_ctx in required_ctx): # formatted string like: 'foo or bar or baz' - formatted_names = "'%s'" % " or ".join(name) + formatted_names = "'%s'" % " or ".join(required_ctx) missing_contexts.append(formatted_names) else: - if name not in input_context: - missing_contexts.append(name) + if not self._match(required_ctx, input_contexts): + missing_contexts.append(required_ctx) if missing_contexts: self.fail("The following context(s) are required but missing from " diff --git a/tests/unit/plugins/common/test_validators.py b/tests/unit/plugins/common/test_validators.py index d093ba4889..64182e02eb 100644 --- a/tests/unit/plugins/common/test_validators.py +++ b/tests/unit/plugins/common/test_validators.py @@ -20,6 +20,7 @@ import ddt from rally.common.plugin import plugin from rally.common import validation from rally.plugins.common import validators +from rally.plugins.task.contexts import dummy as dummy_ctx from tests.unit import test @@ -256,27 +257,53 @@ class RequiredContextsValidatorTestCase(test.TestCase): def test_validate_failed(self): validator = validators.RequiredContextsValidator( - contexts=("c1", "c2", "c3")) + contexts=("c1@bar", "c2", "c3")) e = self.assertRaises( validation.ValidationError, validator.validate, self.credentials, {"contexts": {"a": 1}}, None, None) self.assertEqual( "The following context(s) are required but missing from " - "the input task file: c1, c2, c3", e.message) + "the input task file: c1@bar, c2, c3", e.message) @ddt.data( {"config": { - "contexts": {"c1": 1, "c2": 2, "c3": 3, - "b1": 1, "a1": 1}}}, + "contexts": { + "a1": 1, "b1": 1, "c1@foo": 1, "c2": 2, "c3": 3 + } + }}, {"config": { - "contexts": {"c1": 1, "c2": 2, "c3": 3, - "b1": 1, "b2": 2, "a1": 1}}}, + "contexts": { + "a1@foo": 1, "b1": 1, "c1": 1, "c2": 2, "c3": 3, "b2": 2 + } + }}, ) @ddt.unpack def test_validate_with_or(self, config): + @dummy_ctx.context.configure(name="a1", platform="foo", order=0) + class A1(dummy_ctx.DummyContext): + pass + + @dummy_ctx.context.configure(name="c1", platform="foo", order=0) + class C1(dummy_ctx.DummyContext): + pass + + @dummy_ctx.context.configure(name="c2", platform="foo", order=0) + class C2(dummy_ctx.DummyContext): + pass + + self.addCleanup(A1.unregister) + self.addCleanup(C1.unregister) + self.addCleanup(C2.unregister) + validator = validators.RequiredContextsValidator( - contexts=[("a1", "a2"), "c1", ("b1", "b2"), "c2"]) + contexts=[("a1@foo", "a2"), "c1", ("b1", "b2"), "c2@foo"]) + validator.validate(self.credentials, config, None, None) + + # check arg that should be ignored + validator = validators.RequiredContextsValidator( + "ignore@me", + contexts=[("a1@foo", "a2"), "c1", ("b1", "b2"), "c2@foo"]) validator.validate(self.credentials, config, None, None) def test_validate_with_or_failed(self):