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

Reply via email to