This is an automated email from the ASF dual-hosted git repository. av pushed a commit to branch ignite-ducktape in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/ignite-ducktape by this push: new d292458 Implement custom cluster metadata decorator with support of global cluster_size parametrization (#8464) d292458 is described below commit d2924582961b5350d90546e6e98800b2461dca4d Author: Ivan Daschinskiy <ivanda...@gmail.com> AuthorDate: Wed Nov 18 10:49:30 2020 +0300 Implement custom cluster metadata decorator with support of global cluster_size parametrization (#8464) --- .../ducktests/tests/checks/utils/check_cluster.py | 106 +++++++++++++++++++++ .../{check_marks.py => check_parametrized.py} | 0 .../ignitetest/tests/add_node_rebalance_test.py | 4 +- .../ignitetest/tests/cellular_affinity_test.py | 3 +- .../tests/ignitetest/tests/client_test.py | 7 +- .../tests/control_utility/baseline_test.py | 3 +- .../ignitetest/tests/control_utility/tx_test.py | 5 +- .../tests/ignitetest/tests/discovery_test.py | 3 +- .../tests/ignitetest/tests/pme_free_switch_test.py | 11 ++- .../ducktests/tests/ignitetest/tests/self_test.py | 4 +- .../ducktests/tests/ignitetest/tests/smoke_test.py | 4 +- .../ducktests/tests/ignitetest/utils/__init__.py | 4 +- modules/ducktests/tests/ignitetest/utils/_mark.py | 73 ++++++++++++++ 13 files changed, 198 insertions(+), 29 deletions(-) diff --git a/modules/ducktests/tests/checks/utils/check_cluster.py b/modules/ducktests/tests/checks/utils/check_cluster.py new file mode 100644 index 0000000..9b8dac3 --- /dev/null +++ b/modules/ducktests/tests/checks/utils/check_cluster.py @@ -0,0 +1,106 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +""" +Checks custom cluster metadata decorator. +""" + +from unittest.mock import Mock + +import pytest +from ducktape.cluster.cluster_spec import ClusterSpec, LINUX +from ducktape.mark.mark_expander import MarkedFunctionExpander + +from ignitetest.utils._mark import cluster, ParametrizableClusterMetadata, CLUSTER_SIZE_KEYWORD, CLUSTER_SPEC_KEYWORD + + +def expand_function(*, func, sess_ctx): + """ + Inject parameters into function and generate context list. + """ + assert hasattr(func, "marks") + assert next(filter(lambda x: isinstance(x, ParametrizableClusterMetadata), func.marks), None) + + return MarkedFunctionExpander(session_context=sess_ctx, function=func).expand() + + +def mock_session_ctx(*, cluster_size=None): + """ + Create mock of session context. + """ + sess_ctx = Mock() + sess_ctx.globals = {"cluster_size": cluster_size} if cluster_size is not None else {} + + return sess_ctx + + +# pylint: disable=no-self-use +class CheckClusterParametrization: + """ + Checks custom @cluster parametrization. + """ + def check_num_nodes(self): + """" + Check num_nodes. + """ + @cluster(num_nodes=10) + def function(): + return 0 + + test_context_list = expand_function(func=function, sess_ctx=mock_session_ctx()) + assert len(test_context_list) == 1 + assert test_context_list[0].cluster_use_metadata[CLUSTER_SIZE_KEYWORD] == 10 + + test_context_list = expand_function(func=function, + sess_ctx=mock_session_ctx(cluster_size="100")) + assert len(test_context_list) == 1 + assert test_context_list[0].cluster_use_metadata[CLUSTER_SIZE_KEYWORD] == 100 + + def check_cluster_spec(self): + """" + Check cluster_spec. + """ + @cluster(cluster_spec=ClusterSpec.simple_linux(10)) + def function(): + return 0 + + test_context_list = expand_function(func=function, sess_ctx=mock_session_ctx()) + assert len(test_context_list) == 1 + inserted_spec = test_context_list[0].cluster_use_metadata[CLUSTER_SPEC_KEYWORD] + + assert inserted_spec.size() == 10 + for node in inserted_spec.nodes: + assert node.operating_system == LINUX + + test_context_list = expand_function(func=function, + sess_ctx=mock_session_ctx(cluster_size="100")) + assert len(test_context_list) == 1 + inserted_spec = test_context_list[0].cluster_use_metadata[CLUSTER_SPEC_KEYWORD] + + assert inserted_spec.size() == 100 + for node in inserted_spec.nodes: + assert node.operating_system == LINUX + + def check_invalid_global_param(self): + """Check handle of invalid params.""" + @cluster(num_nodes=10) + def function(): + return 0 + + invalid_vals = ["abc", "-10", "1.5", "0", 1.6, -7, 0] + + for val in invalid_vals: + with pytest.raises(Exception): + expand_function(func=function, sess_ctx=mock_session_ctx(cluster_size=val)) diff --git a/modules/ducktests/tests/checks/utils/check_marks.py b/modules/ducktests/tests/checks/utils/check_parametrized.py similarity index 100% rename from modules/ducktests/tests/checks/utils/check_marks.py rename to modules/ducktests/tests/checks/utils/check_parametrized.py diff --git a/modules/ducktests/tests/ignitetest/tests/add_node_rebalance_test.py b/modules/ducktests/tests/ignitetest/tests/add_node_rebalance_test.py index 82bc730..1a1ee41 100644 --- a/modules/ducktests/tests/ignitetest/tests/add_node_rebalance_test.py +++ b/modules/ducktests/tests/ignitetest/tests/add_node_rebalance_test.py @@ -17,13 +17,11 @@ Module contains node rebalance tests. """ -from ducktape.mark.resource import cluster - from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService from ignitetest.services.utils.ignite_configuration import IgniteConfiguration from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster -from ignitetest.utils import ignite_versions +from ignitetest.utils import ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, IgniteVersion, LATEST diff --git a/modules/ducktests/tests/ignitetest/tests/cellular_affinity_test.py b/modules/ducktests/tests/ignitetest/tests/cellular_affinity_test.py index 8b846eb..3b11434 100644 --- a/modules/ducktests/tests/ignitetest/tests/cellular_affinity_test.py +++ b/modules/ducktests/tests/ignitetest/tests/cellular_affinity_test.py @@ -19,7 +19,6 @@ This module contains Cellular Affinity tests. from enum import IntEnum from ducktape.mark import matrix -from ducktape.mark.resource import cluster from jinja2 import Template from ignitetest.services.ignite import IgniteService @@ -27,7 +26,7 @@ from ignitetest.services.ignite_app import IgniteApplicationService from ignitetest.services.utils.control_utility import ControlUtility from ignitetest.services.utils.ignite_configuration import IgniteConfiguration, IgniteClientConfiguration from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster -from ignitetest.utils import ignite_versions, version_if +from ignitetest.utils import ignite_versions, version_if, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, IgniteVersion, LATEST_2_8 diff --git a/modules/ducktests/tests/ignitetest/tests/client_test.py b/modules/ducktests/tests/ignitetest/tests/client_test.py index 8f5c84a..c6ee4ed 100644 --- a/modules/ducktests/tests/ignitetest/tests/client_test.py +++ b/modules/ducktests/tests/ignitetest/tests/client_test.py @@ -19,14 +19,13 @@ This module contains client tests import time from ducktape.mark import parametrize -from ducktape.mark.resource import cluster from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService from ignitetest.services.utils.control_utility import ControlUtility from ignitetest.services.utils.ignite_configuration import IgniteConfiguration from ignitetest.services.utils.ignite_configuration.cache import CacheConfiguration -from ignitetest.utils import ignite_versions +from ignitetest.utils import ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, V_2_8_1, IgniteVersion @@ -49,8 +48,8 @@ class ClientTest(IgniteTest): JAVA_CLIENT_CLASS_NAME = "org.apache.ignite.internal.ducktest.tests.client_test.IgniteCachePutClient" # pylint: disable=R0913 - @ignite_versions(str(V_2_8_1), str(DEV_BRANCH)) @cluster(num_nodes=7) + @ignite_versions(str(V_2_8_1), str(DEV_BRANCH)) @parametrize(num_nodes=7, static_clients=2, temp_client=3, iteration_count=3, client_work_time=30) def test_ignite_start_stop_nodes(self, ignite_version, num_nodes, static_clients, temp_client, iteration_count, client_work_time): @@ -62,8 +61,8 @@ class ClientTest(IgniteTest): temp_client, iteration_count, client_work_time) # pylint: disable=R0913 - @ignite_versions(str(V_2_8_1), str(DEV_BRANCH)) @cluster(num_nodes=7) + @ignite_versions(str(V_2_8_1), str(DEV_BRANCH)) @parametrize(num_nodes=7, static_clients=2, temp_client=3, iteration_count=3, client_work_time=30) def test_ignite_kill_start_nodes(self, ignite_version, num_nodes, static_clients, temp_client, iteration_count, client_work_time): diff --git a/modules/ducktests/tests/ignitetest/tests/control_utility/baseline_test.py b/modules/ducktests/tests/ignitetest/tests/control_utility/baseline_test.py index a1de09f..710e317 100644 --- a/modules/ducktests/tests/ignitetest/tests/control_utility/baseline_test.py +++ b/modules/ducktests/tests/ignitetest/tests/control_utility/baseline_test.py @@ -17,7 +17,6 @@ This module contains manipulating baseline test through control utility. """ -from ducktape.mark.resource import cluster from ducktape.utils.util import wait_until from ignitetest.services.ignite import IgniteService @@ -25,7 +24,7 @@ from ignitetest.services.utils.control_utility import ControlUtility, ControlUti from ignitetest.services.utils.ignite_configuration import IgniteConfiguration, DataStorageConfiguration from ignitetest.services.utils.ignite_configuration.data_storage import DataRegionConfiguration from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster -from ignitetest.utils import version_if, ignite_versions +from ignitetest.utils import version_if, ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, LATEST_2_8, IgniteVersion, V_2_8_0 diff --git a/modules/ducktests/tests/ignitetest/tests/control_utility/tx_test.py b/modules/ducktests/tests/ignitetest/tests/control_utility/tx_test.py index 5aca06e..fef01b9 100644 --- a/modules/ducktests/tests/ignitetest/tests/control_utility/tx_test.py +++ b/modules/ducktests/tests/ignitetest/tests/control_utility/tx_test.py @@ -17,9 +17,8 @@ """ This module contains transactions manipulation test through control utility. """ -import random -from ducktape.mark.resource import cluster +import random from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService @@ -27,7 +26,7 @@ from ignitetest.services.utils.control_utility import ControlUtility from ignitetest.services.utils.ignite_configuration import IgniteConfiguration from ignitetest.services.utils.ignite_configuration.cache import CacheConfiguration from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster -from ignitetest.utils import ignite_versions +from ignitetest.utils import ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, LATEST_2_8, IgniteVersion diff --git a/modules/ducktests/tests/ignitetest/tests/discovery_test.py b/modules/ducktests/tests/ignitetest/tests/discovery_test.py index 1e8b65d..a4606f1 100644 --- a/modules/ducktests/tests/ignitetest/tests/discovery_test.py +++ b/modules/ducktests/tests/ignitetest/tests/discovery_test.py @@ -25,7 +25,6 @@ from time import monotonic from typing import NamedTuple from ducktape.mark import matrix -from ducktape.mark.resource import cluster from ignitetest.services.ignite import IgniteAwareService, IgniteService, get_event_time, node_failed_event_pattern from ignitetest.services.ignite_app import IgniteApplicationService @@ -35,7 +34,7 @@ from ignitetest.services.utils.ignite_configuration.discovery import from_zookee TcpDiscoverySpi from ignitetest.services.utils.time_utils import epoch_mills from ignitetest.services.zk.zookeeper import ZookeeperService, ZookeeperSettings -from ignitetest.utils import ignite_versions, version_if +from ignitetest.utils import ignite_versions, version_if, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, LATEST_2_8, V_2_8_0, IgniteVersion diff --git a/modules/ducktests/tests/ignitetest/tests/pme_free_switch_test.py b/modules/ducktests/tests/ignitetest/tests/pme_free_switch_test.py index d215343..c182c61 100644 --- a/modules/ducktests/tests/ignitetest/tests/pme_free_switch_test.py +++ b/modules/ducktests/tests/ignitetest/tests/pme_free_switch_test.py @@ -20,7 +20,6 @@ This module contains PME free switch tests. import time from ducktape.mark import matrix -from ducktape.mark.resource import cluster from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService @@ -28,7 +27,7 @@ from ignitetest.services.utils.control_utility import ControlUtility from ignitetest.services.utils.ignite_configuration import IgniteConfiguration from ignitetest.services.utils.ignite_configuration.cache import CacheConfiguration from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster -from ignitetest.utils import ignite_versions +from ignitetest.utils import ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, LATEST_2_7, V_2_8_0, IgniteVersion @@ -61,7 +60,8 @@ class PmeFreeSwitchTest(IgniteTest): config = IgniteConfiguration(version=IgniteVersion(ignite_version), caches=caches, cluster_state="INACTIVE") - ignites = IgniteService(self.test_context, config, num_nodes=self.NUM_NODES) + num_nodes = len(self.test_context.cluster) - 2 + ignites = IgniteService(self.test_context, config, num_nodes=num_nodes) ignites.start() @@ -71,7 +71,7 @@ class PmeFreeSwitchTest(IgniteTest): ControlUtility(ignites, self.test_context).activate() client_config = config._replace(client_mode=True, - discovery_spi=from_ignite_cluster(ignites, slice(0, self.NUM_NODES - 1))) + discovery_spi=from_ignite_cluster(ignites, slice(0, num_nodes - 1))) long_tx_streamer = IgniteApplicationService( self.test_context, @@ -93,7 +93,7 @@ class PmeFreeSwitchTest(IgniteTest): single_key_tx_streamer.start() - ignites.stop_node(ignites.nodes[self.NUM_NODES - 1]) + ignites.stop_node(ignites.nodes[num_nodes - 1]) if long_txs: long_tx_streamer.await_event("Node left topology", 60, from_the_beginning=True) @@ -109,5 +109,6 @@ class PmeFreeSwitchTest(IgniteTest): data["Worst latency (ms)"] = single_key_tx_streamer.extract_result("WORST_LATENCY") data["Streamed txs"] = single_key_tx_streamer.extract_result("STREAMED") data["Measure duration (ms)"] = single_key_tx_streamer.extract_result("MEASURE_DURATION") + data["Server nodes"] = num_nodes return data diff --git a/modules/ducktests/tests/ignitetest/tests/self_test.py b/modules/ducktests/tests/ignitetest/tests/self_test.py index 6d1a159..fbeda05 100644 --- a/modules/ducktests/tests/ignitetest/tests/self_test.py +++ b/modules/ducktests/tests/ignitetest/tests/self_test.py @@ -17,14 +17,12 @@ This module contains smoke tests that checks that ducktape works as expected """ -from ducktape.mark.resource import cluster - from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService from ignitetest.services.ignite_execution_exception import IgniteExecutionException from ignitetest.services.utils.ignite_configuration import IgniteConfiguration, IgniteClientConfiguration from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster -from ignitetest.utils import ignite_versions +from ignitetest.utils import ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, IgniteVersion diff --git a/modules/ducktests/tests/ignitetest/tests/smoke_test.py b/modules/ducktests/tests/ignitetest/tests/smoke_test.py index f58d3c5..da79634 100644 --- a/modules/ducktests/tests/ignitetest/tests/smoke_test.py +++ b/modules/ducktests/tests/ignitetest/tests/smoke_test.py @@ -17,15 +17,13 @@ This module contains smoke tests that checks that services work """ -from ducktape.mark.resource import cluster - from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService from ignitetest.services.spark import SparkService from ignitetest.services.utils.ignite_configuration.discovery import from_ignite_cluster from ignitetest.services.utils.ignite_configuration import IgniteConfiguration from ignitetest.services.zk.zookeeper import ZookeeperService -from ignitetest.utils import ignite_versions +from ignitetest.utils import ignite_versions, cluster from ignitetest.utils.ignite_test import IgniteTest from ignitetest.utils.version import DEV_BRANCH, IgniteVersion diff --git a/modules/ducktests/tests/ignitetest/utils/__init__.py b/modules/ducktests/tests/ignitetest/utils/__init__.py index 2f557ee..a400779 100644 --- a/modules/ducktests/tests/ignitetest/utils/__init__.py +++ b/modules/ducktests/tests/ignitetest/utils/__init__.py @@ -17,6 +17,6 @@ This module contains convenient utils for test. """ -from ._mark import version_if, ignite_versions +from ._mark import version_if, ignite_versions, cluster -__all__ = ['version_if', 'ignite_versions'] +__all__ = ['version_if', 'ignite_versions', 'cluster'] diff --git a/modules/ducktests/tests/ignitetest/utils/_mark.py b/modules/ducktests/tests/ignitetest/utils/_mark.py index 56bfabd..d6d0bdb 100644 --- a/modules/ducktests/tests/ignitetest/utils/_mark.py +++ b/modules/ducktests/tests/ignitetest/utils/_mark.py @@ -18,7 +18,9 @@ Module contains useful test decorators. """ from collections.abc import Iterable +import copy +from ducktape.cluster.cluster_spec import ClusterSpec from ducktape.mark._mark import Ignore, Mark, _inject from ignitetest.utils.version import IgniteVersion @@ -121,6 +123,62 @@ class IgniteVersionParametrize(Mark): return super().__eq__(other) and self.versions == other.versions +CLUSTER_SPEC_KEYWORD = "cluster_spec" +CLUSTER_SIZE_KEYWORD = "num_nodes" + + +class ParametrizableClusterMetadata(Mark): + """Provide a hint about how a given test will use the cluster.""" + + def __init__(self, **kwargs): + self.metadata = copy.copy(kwargs) + + @property + def name(self): + return "PARAMETRIZABLE_RESOURCE_HINT_CLUSTER_USE" + + def apply(self, seed_context, context_list): + assert len(context_list) > 0, "parametrizable cluster use annotation is not being applied to any test cases" + + cluster_size_param = self._extract_cluster_size(seed_context) + + for ctx in context_list: + if cluster_size_param: + self._modify_metadata(cluster_size_param) + + if not ctx.cluster_use_metadata: + ctx.cluster_use_metadata = self.metadata + + return context_list + + @staticmethod + def _extract_cluster_size(seed_context): + cluster_size = seed_context.globals.get('cluster_size') + + if cluster_size is None: + return None + + res = int(cluster_size) if isinstance(cluster_size, str) else cluster_size + + if not isinstance(res, int): + raise ValueError(f"Expected string or int param, {cluster_size} of {type(cluster_size)} passed instead.") + + if res <= 0: + raise ValueError(f"Expected cluster_size greater than 0, {cluster_size} passed instead.") + + return res + + def _modify_metadata(self, new_size): + cluster_spec = self.metadata.get(CLUSTER_SPEC_KEYWORD) + cluster_size = self.metadata.get(CLUSTER_SIZE_KEYWORD) + + if cluster_spec is not None and not cluster_spec.empty(): + node_spec = next(iter(cluster_spec)) + self.metadata[CLUSTER_SPEC_KEYWORD] = ClusterSpec.from_nodes([node_spec] * new_size) + elif cluster_size is not None: + self.metadata[CLUSTER_SIZE_KEYWORD] = new_size + + def ignite_versions(*args, version_prefix="ignite_version"): """ Decorate test function to inject ignite versions. Versions will be overriden by globals "ignite_versions" param. @@ -146,3 +204,18 @@ def version_if(condition, *, variable_name='ignite_version'): return func return ignorer + + +def cluster(**kwargs): + """Test method decorator used to provide hints about how the test will use the given cluster. + + :Keywords: + + - ``num_nodes`` provide hint about how many nodes the test will consume + - ``cluster_spec`` provide hint about how many nodes of each type the test will consume + """ + def cluster_use_metadata_adder(func): + Mark.mark(func, ParametrizableClusterMetadata(**kwargs)) + return func + + return cluster_use_metadata_adder