You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by av...@apache.org on 2021/10/13 16:44:17 UTC

[ignite] branch ignite-15333 created (now b19b5b1)

This is an automated email from the ASF dual-hosted git repository.

av pushed a change to branch ignite-15333
in repository https://gitbox.apache.org/repos/asf/ignite.git.


      at b19b5b1  WIP

This branch includes the following new commits:

     new b19b5b1  WIP

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[ignite] 01/01: WIP

Posted by av...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

av pushed a commit to branch ignite-15333
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit b19b5b1590ddb11f29cc17e6f9e7d9875147d91f
Author: Anton Vinogradov <av...@apache.org>
AuthorDate: Wed Oct 13 19:42:43 2021 +0300

    WIP
---
 config/ignite-log4j.xml                            |  17 ++++
 .../events/CacheConsistencyViolationEvent.java     |  13 +++
 .../GridNearReadRepairAbstractFuture.java          |   1 +
 .../consistency/VisorConsistencyRepairTask.java    |  31 +++++-
 .../InconsistentNodeApplication.java               | 102 +++++++++++++++++++
 .../ducktests/src/main/resources/log4j.properties  |  25 -----
 .../ignitetest/services/utils/control_utility.py   |   8 ++
 .../ignitetest/services/utils/ignite_aware.py      |  17 ++--
 .../utils/ignite_configuration/__init__.py         |   2 +
 .../tests/ignitetest/services/utils/ignite_spec.py |   3 +-
 .../tests/ignitetest/services/utils/path.py        |   4 +-
 .../services/utils/templates/ignite.xml.j2         |   2 +-
 .../services/utils/templates/log4j.xml.j2          |   2 +-
 .../tests/control_utility/consistency_test.py      | 113 +++++++++++++++++++++
 14 files changed, 298 insertions(+), 42 deletions(-)

diff --git a/config/ignite-log4j.xml b/config/ignite-log4j.xml
index 7669af3..f53997d 100644
--- a/config/ignite-log4j.xml
+++ b/config/ignite-log4j.xml
@@ -117,6 +117,23 @@
         <level value="WARN"/>
     </category>
 
+    <!--
+        Consistency repair section.
+        Repaired data should not be logged to the same log file by default.
+    -->
+    <category name="org.apache.ignite.internal.visor.consistency" additivity="false">
+        <appender-ref ref="CONSISTENCY"/>
+        <level value="INFO"/>
+    </category>
+
+    <appender name="CONSISTENCY" class="org.apache.log4j.FileAppender">
+        <param name="File" value="${IGNITE_HOME}/work/log/consistency.log"/>
+        <param name="Append" value="true"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[%d{ISO8601}][%-5p][%t][%c{1}] %m%n"/>
+        </layout>
+    </appender>
+
     <!-- Default settings. -->
     <root>
         <!-- Print out all info by default. -->
diff --git a/modules/core/src/main/java/org/apache/ignite/events/CacheConsistencyViolationEvent.java b/modules/core/src/main/java/org/apache/ignite/events/CacheConsistencyViolationEvent.java
index 27c070e..1c93e44 100644
--- a/modules/core/src/main/java/org/apache/ignite/events/CacheConsistencyViolationEvent.java
+++ b/modules/core/src/main/java/org/apache/ignite/events/CacheConsistencyViolationEvent.java
@@ -69,19 +69,25 @@ public class CacheConsistencyViolationEvent extends EventAdapter {
     /** Represents original values of entries.*/
     final Map<Object, Map<ClusterNode, EntryInfo>> entries;
 
+    /** Cache name. */
+    final String cacheName;
+
     /**
      * Creates a new instance of CacheConsistencyViolationEvent.
      *
+     * @param cacheName Cache name.
      * @param node Local node.
      * @param msg Event message.
      * @param entries Collection of original entries.
      */
     public CacheConsistencyViolationEvent(
+        String cacheName,
         ClusterNode node,
         String msg,
         Map<Object, Map<ClusterNode, EntryInfo>> entries) {
         super(node, msg, EVT_CONSISTENCY_VIOLATION);
 
+        this.cacheName = cacheName;
         this.entries = entries;
     }
 
@@ -95,6 +101,13 @@ public class CacheConsistencyViolationEvent extends EventAdapter {
     }
 
     /**
+     * Returns cache name.
+     */
+    public String getCacheName() {
+        return cacheName;
+    }
+
+    /**
      * Inconsistent entry info.
      */
     public interface EntryInfo {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/consistency/GridNearReadRepairAbstractFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/consistency/GridNearReadRepairAbstractFuture.java
index 6db9485..61fd137 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/consistency/GridNearReadRepairAbstractFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/consistency/GridNearReadRepairAbstractFuture.java
@@ -295,6 +295,7 @@ public abstract class GridNearReadRepairAbstractFuture extends GridFutureAdapter
         }
 
         evtMgr.record(new CacheConsistencyViolationEvent(
+            ctx.name(),
             ctx.discovery().localNode(),
             "Consistency violation fixed.",
             originalMap));
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/consistency/VisorConsistencyRepairTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/consistency/VisorConsistencyRepairTask.java
index a43a6b0..ddf6af0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/consistency/VisorConsistencyRepairTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/consistency/VisorConsistencyRepairTask.java
@@ -125,6 +125,7 @@ public class VisorConsistencyRepairTask extends
             String cacheName = arg.cacheName();
             int p = arg.part();
             int batchSize = 1024;
+            int statusDelay = 60_000; // Every minute.
 
             IgniteInternalCache<Object, Object> internalCache = ignite.context().cache().cache(cacheName);
 
@@ -146,7 +147,10 @@ public class VisorConsistencyRepairTask extends
             if (part == null)
                 return null; // Partition does not belong to the node.
 
+            log.info("Consistency check started [grp=" + grpCtx.cacheOrGroupName() + ", part=" + p + "]");
+
             long cnt = 0;
+            long statusTs = 0;
 
             part.reserve();
 
@@ -171,17 +175,33 @@ public class VisorConsistencyRepairTask extends
                             keys.add(row.key());
                         }
 
+                        if (keys.isEmpty()) {
+                            log.info("Consistency check finished [grp=" + grpCtx.cacheOrGroupName() +
+                                ", part=" + p + ", checked=" + cnt + "]");
+
+                            break;
+                        }
+
                         try {
                             cache.getAll(keys); // Repair.
-
-                            cnt += keys.size();
                         }
                         catch (CacheException e) {
-                            if (!(e.getCause() instanceof IgniteConsistencyViolationException) && !isCancelled())
+                            if (!(e.getCause() instanceof IgniteConsistencyViolationException) // Found but not fixed.
+                                && !isCancelled())
                                 throw new IgniteException("Read repair attempt failed.", e);
                         }
+
+                        cnt += keys.size();
+
+                        if (System.currentTimeMillis() >= statusTs) {
+                            statusTs = System.currentTimeMillis() + statusDelay;
+
+                            log.info("Consistency check progress [grp=" + grpCtx.cacheOrGroupName() +
+                                ", part=" + p + ", checked=" + cnt + "/" + part.fullSize() + "]");
+                        }
+
                     }
-                    while (!keys.isEmpty() && !isCancelled());
+                    while (!isCancelled());
                 }
                 finally {
                     ignite.events().stopLocalListen(lsnr);
@@ -218,7 +238,8 @@ public class VisorConsistencyRepairTask extends
 
                     found++;
 
-                    sb.append("Key: ").append(key).append("\n");
+                    sb.append("Key: ").append(key)
+                        .append(" (Cache: ").append(evt.getCacheName()).append(")").append("\n");
 
                     for (Map.Entry<ClusterNode, CacheConsistencyViolationEvent.EntryInfo> mapping : entry.getValue().entrySet()) {
                         ClusterNode node = mapping.getKey();
diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/control_utility/InconsistentNodeApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/control_utility/InconsistentNodeApplication.java
new file mode 100644
index 0000000..b92e39f
--- /dev/null
+++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/control_utility/InconsistentNodeApplication.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.ducktest.tests.control_utility;
+
+import java.util.concurrent.ThreadLocalRandom;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.ducktest.utils.IgniteAwareApplication;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheObjectImpl;
+import org.apache.ignite.internal.processors.cache.GridCacheAdapter;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryEx;
+import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersionManager;
+import org.apache.ignite.internal.processors.dr.GridDrType;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+
+/**
+ *
+ */
+public class InconsistentNodeApplication extends IgniteAwareApplication {
+    /**
+     * {@inheritDoc}
+     */
+    @Override protected void run(JsonNode jsonNode) throws Exception {
+        String cacheName = jsonNode.get("cacheName").asText();
+        int amount = jsonNode.get("amount").asInt();
+        int parts =  jsonNode.get("parts").asInt();
+        boolean tx = jsonNode.get("tx").asBoolean();
+
+        markInitialized();
+
+        waitForActivation();
+
+        CacheConfiguration<Integer, Integer> cfg = new CacheConfiguration<>(cacheName);
+
+        cfg.setAtomicityMode(tx ? TRANSACTIONAL : ATOMIC);
+        cfg.setCacheMode(CacheMode.REPLICATED);
+        cfg.setAffinity(new RendezvousAffinityFunction().setPartitions(parts));
+
+        ignite.getOrCreateCache(cfg);
+
+        GridCacheVersionManager mgr =
+            ((GridCacheAdapter)((IgniteEx)ignite).cachex(cacheName).cache()).context().shared().versions();
+
+        int cnt = 0;
+
+        for (int key = 0; key < amount; key += ThreadLocalRandom.current().nextInt(1, 3)) { // Random shift.
+            IgniteInternalCache<?, ?> cache = ((IgniteEx)ignite).cachex(cacheName);
+
+            GridCacheAdapter<?,?> adapter = (GridCacheAdapter)cache.cache();
+
+            GridCacheEntryEx entry = adapter.entryEx(key);
+
+            boolean init = entry.initialValue(
+                new CacheObjectImpl(cnt, null), // Incremental value.
+                mgr.next(entry.context().kernalContext().discovery().topologyVersion()), // Incremental version.
+                0,
+                0,
+                false,
+                AffinityTopologyVersion.NONE,
+                GridDrType.DR_NONE,
+                false,
+                false);
+
+            assert init : "iterableKey " + key + " already inited";
+
+            if (cnt % 1_000 == 0)
+                log.info("APPLICATION_STREAMED [entries=" + cnt + "]");
+
+            cnt++;
+        }
+
+        log.info("APPLICATION_STREAMING_FINISHED [entries=" + cnt + "]");
+
+        while (!terminated())
+            U.sleep(100); // Keeping node alive.
+
+        markFinished();
+    }
+}
diff --git a/modules/ducktests/src/main/resources/log4j.properties b/modules/ducktests/src/main/resources/log4j.properties
deleted file mode 100644
index ecfe84a..0000000
--- a/modules/ducktests/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# 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.
-#
-
-# Root logger option
-log4j.rootLogger=INFO, stdout
-
-# Direct log messages to stdout
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Target=System.out
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601}][%-5p][%t][%c{1}] %m%n
diff --git a/modules/ducktests/tests/ignitetest/services/utils/control_utility.py b/modules/ducktests/tests/ignitetest/services/utils/control_utility.py
index 98230f9..98a2de1 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/control_utility.py
+++ b/modules/ducktests/tests/ignitetest/services/utils/control_utility.py
@@ -175,6 +175,14 @@ class ControlUtility:
 
         return re.search(r'/.*.txt', data).group(0)
 
+    def check_consistency(self, args):
+        """
+        Consistency check.
+        """
+        data = self.__run(f"--consistency {args} --enable-experimental")
+
+        assert ('Command [CONSISTENCY] finished with code: 0' in data), data
+
     def snapshot_create(self, snapshot_name: str, timeout_sec: int = 60):
         """
         Create snapshot.
diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py
index dcbfba2..99d999a 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py
+++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py
@@ -242,8 +242,8 @@ class IgniteAwareService(BackgroundThreadService, IgnitePathAware, metaclass=ABC
         """
         return len(self.pids(node)) > 0
 
-    @staticmethod
-    def await_event_on_node(evt_message, node, timeout_sec, from_the_beginning=False, backoff_sec=.1):
+    def await_event_on_node(self, evt_message, node, timeout_sec, from_the_beginning=False, backoff_sec=.1,
+                            log_file=None):
         """
         Await for specific event message in a node's log file.
         :param evt_message: Event message.
@@ -252,13 +252,15 @@ class IgniteAwareService(BackgroundThreadService, IgnitePathAware, metaclass=ABC
         :param from_the_beginning: If True, search for message from the beginning of log file.
         :param backoff_sec: Number of seconds to back off between each failure to meet the condition
                 before checking again.
+        :param log_file: Explicit log file.
         """
-        with monitor_log(node, node.log_file, from_the_beginning) as monitor:
+        with monitor_log(node, os.path.join(self.log_dir, log_file) if log_file else node.log_file,
+                         from_the_beginning) as monitor:
             monitor.wait_until(evt_message, timeout_sec=timeout_sec, backoff_sec=backoff_sec,
                                err_msg="Event [%s] was not triggered on '%s' in %d seconds" % (evt_message, node.name,
                                                                                                timeout_sec))
 
-    def await_event(self, evt_message, timeout_sec, from_the_beginning=False, backoff_sec=.1):
+    def await_event(self, evt_message, timeout_sec, from_the_beginning=False, backoff_sec=.1, log_file=None):
         """
         Await for specific event messages on all nodes.
         :param evt_message: Event message.
@@ -266,10 +268,11 @@ class IgniteAwareService(BackgroundThreadService, IgnitePathAware, metaclass=ABC
         :param from_the_beginning: If True, search for message from the beggining of log file.
         :param backoff_sec: Number of seconds to back off between each failure to meet the condition
                 before checking again.
+        :param log_file: Explicit log file.
         """
         for node in self.nodes:
             self.await_event_on_node(evt_message, node, timeout_sec, from_the_beginning=from_the_beginning,
-                                     backoff_sec=backoff_sec)
+                                     backoff_sec=backoff_sec, log_file=log_file)
 
     @staticmethod
     def event_time(evt_message, node):
@@ -484,7 +487,9 @@ class IgniteAwareService(BackgroundThreadService, IgnitePathAware, metaclass=ABC
         Update the node log file.
         """
         if not hasattr(node, 'log_file'):
-            node.log_file = os.path.join(self.log_dir, "ignite.log")
+            # '*' here is to support LoggerNodeIdAndApplicationAware loggers generates logs like 'ignite-367efed9.log'
+            # default Ignite configuration uses o.a.i.l.l.Log4jRollingFileAppender generates such files.
+            node.log_file = os.path.join(self.log_dir, "ignite*.log")
 
         cnt = list(node.account.ssh_capture(f'ls {self.log_dir} | '
                                             f'grep -E "^ignite.log(.[0-9]+)?$" | '
diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py
index 0f41970..82ceca0 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py
+++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_configuration/__init__.py
@@ -21,6 +21,7 @@ from typing import NamedTuple
 
 from ignitetest.services.utils import IgniteServiceType
 from ignitetest.services.utils.ignite_configuration.communication import CommunicationSpi, TcpCommunicationSpi
+from ignitetest.services.utils.path import IgnitePathAware
 from ignitetest.services.utils.ssl.client_connector_configuration import ClientConnectorConfiguration
 from ignitetest.services.utils.ssl.connector_configuration import ConnectorConfiguration
 from ignitetest.services.utils.ignite_configuration.data_storage import DataStorageConfiguration
@@ -63,6 +64,7 @@ class IgniteConfiguration(NamedTuple):
     local_event_listeners: str = None
     include_event_types: str = None
     event_storage_spi: str = None
+    log4j_config: str = IgnitePathAware.IGNITE_LOG_CONFIG_NAME
 
     def __prepare_ssl(self, test_globals, shared_root):
         """
diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py
index 61bfa9b..57f9fb3 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py
+++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py
@@ -90,7 +90,6 @@ class IgniteSpec(metaclass=ABCMeta):
                                                 oom_path=os.path.join(service.log_dir, "ignite_out_of_mem.hprof"))
 
         self._add_jvm_opts(["-DIGNITE_SUCCESS_FILE=" + os.path.join(self.service.persistent_root, "success_file"),
-                            "-Dlog4j.configuration=file:" + self.service.log_config_file,
                             "-Dlog4j.configDebug=true"])
 
         if service.context.globals.get(JFR_ENABLED, False):
@@ -162,7 +161,7 @@ class IgniteSpec(metaclass=ABCMeta):
         """
         return {
             'EXCLUDE_TEST_CLASSES': 'true',
-            'IGNITE_LOG_DIR': self.service.persistent_root,
+            'IGNITE_LOG_DIR': self.service.log_dir,
             'USER_LIBS': ":".join(self.libs())
         }
 
diff --git a/modules/ducktests/tests/ignitetest/services/utils/path.py b/modules/ducktests/tests/ignitetest/services/utils/path.py
index 06b2398..6a915b4 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/path.py
+++ b/modules/ducktests/tests/ignitetest/services/utils/path.py
@@ -67,7 +67,7 @@ class PathAware:
         After changing to property based logs, will be removed.
         """
         setattr(self, 'logs', {
-            "logs": {
+            "log": {
                 "path": self.log_dir,
                 "collect_default": True
             },
@@ -185,7 +185,7 @@ class IgnitePathAware(PathAware, metaclass=ABCMeta):
 
     IGNITE_THIN_CLIENT_CONFIG_NAME = "ignite-thin-config.xml"
 
-    IGNITE_LOG_CONFIG_NAME = "ignite-log4j.xml"
+    IGNITE_LOG_CONFIG_NAME = "ignite-ducktape-log4j.xml"
 
     @property
     def config_file(self):
diff --git a/modules/ducktests/tests/ignitetest/services/utils/templates/ignite.xml.j2 b/modules/ducktests/tests/ignitetest/services/utils/templates/ignite.xml.j2
index 2cd7370..fb7c204 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/templates/ignite.xml.j2
+++ b/modules/ducktests/tests/ignitetest/services/utils/templates/ignite.xml.j2
@@ -34,7 +34,7 @@
         <property name="workDirectory" value="{{ service.work_dir }}" />
         <property name="gridLogger">
             <bean class="org.apache.ignite.logger.log4j.Log4JLogger">
-                <constructor-arg type="java.lang.String" value="{{ service.config_dir }}/ignite-log4j.xml"/>
+                <constructor-arg type="java.lang.String" value="{{ service.config_dir }}/{{ config.log4j_config }}"/>
             </bean>
         </property>
 
diff --git a/modules/ducktests/tests/ignitetest/services/utils/templates/log4j.xml.j2 b/modules/ducktests/tests/ignitetest/services/utils/templates/log4j.xml.j2
index 06e8e12..058a2be 100644
--- a/modules/ducktests/tests/ignitetest/services/utils/templates/log4j.xml.j2
+++ b/modules/ducktests/tests/ignitetest/services/utils/templates/log4j.xml.j2
@@ -24,7 +24,7 @@
         <param name="File" value="{{ service.log_dir }}/ignite.log"/>
         <param name="Append" value="true"/>
         <layout class="org.apache.log4j.PatternLayout">
-            <param name="ConversionPattern" value="[%d{{ISO8601}}][%-5p][%t][%c{{1}}] %m%n"/>
+            <param name="ConversionPattern" value="[%d{ISO8601}][%-5p][%t][%c{1}] %m%n"/>
         </layout>
     </appender>
 
diff --git a/modules/ducktests/tests/ignitetest/tests/control_utility/consistency_test.py b/modules/ducktests/tests/ignitetest/tests/control_utility/consistency_test.py
new file mode 100644
index 0000000..fe4ade4
--- /dev/null
+++ b/modules/ducktests/tests/ignitetest/tests/control_utility/consistency_test.py
@@ -0,0 +1,113 @@
+#  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.
+#
+#       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.
+
+
+"""
+This module contains consistency check/repair tests.
+"""
+
+from ducktape.errors import TimeoutError
+
+from ignitetest.services.ignite_app import IgniteApplicationService
+from ignitetest.services.ignite_execution_exception import IgniteExecutionException
+from ignitetest.services.utils.control_utility import ControlUtility
+from ignitetest.services.utils.ignite_configuration import IgniteConfiguration
+from ignitetest.utils import cluster, ignite_versions
+from ignitetest.utils.ignite_test import IgniteTest
+from ignitetest.utils.version import DEV_BRANCH, IgniteVersion
+
+
+class ConsistencyTest(IgniteTest):
+    """
+    Consistency test.
+    """
+    CACHE_NAME = "TEST"
+
+    PROPERTIES = """
+        <property name="includeEventTypes">
+            <util:constant static-field="org.apache.ignite.events.EventType.EVT_CONSISTENCY_VIOLATION"/>
+        </property>
+        """
+
+    @cluster(num_nodes=2)
+    @ignite_versions(str(DEV_BRANCH))
+    def test_logging(self, ignite_version):
+        """
+        Tests logging goes to the correct file (consistency.log) when default AI config used.
+        """
+        cfg_filename = "ignite-default-log4j.xml"
+
+        ignites = IgniteApplicationService(
+            self.test_context,
+            IgniteConfiguration(
+                version=IgniteVersion(ignite_version),
+                properties=self.PROPERTIES,
+                log4j_config=cfg_filename  # default AI config (will be generated below)
+            ),
+            java_class_name="org.apache.ignite.internal.ducktest.tests.control_utility.InconsistentNodeApplication",
+            params={
+                "cacheName": self.CACHE_NAME,
+                "amount": 1024,
+                "parts": 1,
+                "tx": False
+            },
+            startup_timeout_sec=180,
+            num_nodes=len(self.test_context.cluster))
+
+        for node in ignites.nodes:  # copying default AI config with log path replacement
+            ignites.init_persistent(node)
+
+            cfg_file = f"{ignites.config_dir}/{cfg_filename}"
+
+            ignites.exec_command(node, f"cp {ignites.home_dir}/config/ignite-log4j.xml {cfg_file}")
+
+            orig = "${IGNITE_HOME}/work/log".replace('/', '\\/')
+            fixed = ignites.log_dir.replace('/', '\\/')
+
+            ignites.exec_command(node, f"sed -i 's/{orig}/{fixed}/g' {cfg_file}")
+
+        ignites.start()
+
+        control_utility = ControlUtility(ignites)
+
+        control_utility.activate()
+
+        ignites.await_event("APPLICATION_STREAMING_FINISHED", 60, from_the_beginning=True)
+
+        try:
+            control_utility.idle_verify()  # making sure we have broken data
+            raise IgniteExecutionException("Fail.")
+        except AssertionError:
+            pass
+
+        control_utility.check_consistency(f"repair {self.CACHE_NAME} 0")  # checking/repairing
+
+        message = "Cache consistency violations recorded."
+
+        ignites.await_event(message, 60, from_the_beginning=True, log_file="consistency.log")
+
+        try:
+            ignites.await_event(message, 10, from_the_beginning=True)
+            raise IgniteExecutionException("Fail.")
+        except TimeoutError:
+            pass