This is an automated email from the ASF dual-hosted git repository.
rpuch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 843cb32a3e7 IGNITE-27044 Extract tests restarting nodes from
ItBuildIndexTest (#6966)
843cb32a3e7 is described below
commit 843cb32a3e742d02bcf6b1ff682ddf6fb93a74b5
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Thu Nov 13 18:43:41 2025 +0400
IGNITE-27044 Extract tests restarting nodes from ItBuildIndexTest (#6966)
---
.../internal/index/ItBuildIndexOneNodeTest.java | 10 +-
.../ignite/internal/index/ItBuildIndexTest.java | 141 +------------
.../ItBuildIndexWriteIntentsHandlingTest.java | 230 +++++++++++++++++++++
.../internal/index/WriteIntentSwitchControl.java | 31 +++
.../internal/ClusterPerClassIntegrationTest.java | 2 +-
5 files changed, 269 insertions(+), 145 deletions(-)
diff --git
a/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexOneNodeTest.java
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexOneNodeTest.java
index 6b38736040e..6b6d68d5218 100644
---
a/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexOneNodeTest.java
+++
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexOneNodeTest.java
@@ -21,6 +21,7 @@ import static
java.util.concurrent.CompletableFuture.failedFuture;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_PROFILE;
+import static
org.apache.ignite.internal.index.WriteIntentSwitchControl.disableWriteIntentSwitchExecution;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static
org.apache.ignite.internal.sql.engine.util.QueryChecker.containsIndexScan;
import static org.apache.ignite.internal.table.TableTestUtils.getIndexStrict;
@@ -60,7 +61,6 @@ import org.apache.ignite.internal.storage.index.IndexRow;
import org.apache.ignite.internal.storage.index.IndexStorage;
import org.apache.ignite.internal.storage.index.SortedIndexStorage;
import org.apache.ignite.internal.table.TableViewInternal;
-import org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicaRequest;
import org.apache.ignite.internal.util.Cursor;
import org.apache.ignite.sql.SqlException;
import org.apache.ignite.table.Table;
@@ -348,7 +348,7 @@ public class ItBuildIndexOneNodeTest extends
BaseSqlIntegrationTest {
void writeIntentFromCommittedTxShouldBeIndexed() throws Exception {
createZoneAndTable(ZONE_NAME, TABLE_NAME, 1, 1);
- disableWriteIntentSwitchExecution();
+ disableWriteIntentSwitchExecution(CLUSTER);
Transaction tx = node().transactions().begin();
insertDataInTransaction(tx, TABLE_NAME, List.of("ID", "NAME",
"SALARY"), new Object[]{0, "0", 10.0});
@@ -371,7 +371,7 @@ public class ItBuildIndexOneNodeTest extends
BaseSqlIntegrationTest {
void writeIntentFromAbortedTxShouldNotBeIndexed() throws Exception {
createZoneAndTable(ZONE_NAME, TABLE_NAME, 1, 1);
- disableWriteIntentSwitchExecution();
+ disableWriteIntentSwitchExecution(CLUSTER);
Transaction tx = node().transactions().begin();
insertDataInTransaction(tx, TABLE_NAME, List.of("ID", "NAME",
"SALARY"), new Object[]{0, "0", 10.0});
@@ -390,10 +390,6 @@ public class ItBuildIndexOneNodeTest extends
BaseSqlIntegrationTest {
assertFalse(hasSomethingInIndex, "Nothing should have been put to the
index");
}
- private static void disableWriteIntentSwitchExecution() {
- node().dropMessages((recipientId, message) -> message instanceof
WriteIntentSwitchReplicaRequest);
- }
-
private static IgniteImpl node() {
return unwrapIgniteImpl(CLUSTER.node(0));
}
diff --git
a/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexTest.java
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexTest.java
index 9bc5cbe05db..bb2eaa49e4e 100644
---
a/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexTest.java
+++
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexTest.java
@@ -28,7 +28,6 @@ import static
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static
org.apache.ignite.internal.lang.IgniteSystemProperties.colocationEnabled;
import static
org.apache.ignite.internal.sql.engine.util.QueryChecker.containsIndexScan;
-import static org.apache.ignite.internal.table.TableTestUtils.getIndexStrict;
import static
org.apache.ignite.internal.table.distributed.storage.InternalTableImpl.AWAIT_PRIMARY_REPLICA_TIMEOUT;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
@@ -37,7 +36,6 @@ import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.HashMap;
@@ -57,7 +55,6 @@ import
org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
import org.apache.ignite.internal.hlc.HybridClock;
import org.apache.ignite.internal.hlc.HybridTimestamp;
-import
org.apache.ignite.internal.index.message.IsNodeFinishedRwTransactionsStartedBeforeRequest;
import org.apache.ignite.internal.network.NetworkMessage;
import
org.apache.ignite.internal.partition.replicator.network.command.BuildIndexCommand;
import org.apache.ignite.internal.partitiondistribution.Assignment;
@@ -70,22 +67,15 @@ import
org.apache.ignite.internal.replicator.TablePartitionId;
import org.apache.ignite.internal.replicator.ZonePartitionId;
import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
import org.apache.ignite.internal.sql.SqlCommon;
-import org.apache.ignite.internal.storage.StorageException;
-import org.apache.ignite.internal.storage.index.IndexRow;
import org.apache.ignite.internal.storage.index.IndexStorage;
-import org.apache.ignite.internal.storage.index.SortedIndexStorage;
import org.apache.ignite.internal.table.InternalTable;
import org.apache.ignite.internal.table.NodeUtils;
import org.apache.ignite.internal.table.TableViewInternal;
-import org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicaRequest;
-import org.apache.ignite.internal.util.Cursor;
import org.apache.ignite.raft.jraft.rpc.WriteActionRequest;
import org.apache.ignite.table.Table;
-import org.apache.ignite.tx.Transaction;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -223,129 +213,6 @@ public class ItBuildIndexTest extends
BaseSqlIntegrationTest {
assertThat(sendBuildIndexCommandFuture, willSucceedFast());
}
- @Test
- void writeIntentFromTxAbandonedBeforeShouldNotBeIndexed() throws Exception
{
- createTable(1, 1);
-
- disableWriteIntentSwitchExecution();
-
- // Create and abandon a transaction.
- int txCoordinatorOrdinal = 2;
- Transaction tx =
CLUSTER.node(txCoordinatorOrdinal).transactions().begin();
- insertDataInTransaction(tx, TABLE_NAME, List.of("I0", "I1"), new
Object[]{1, 1});
-
- CLUSTER.restartNode(txCoordinatorOrdinal);
-
- createIndex(INDEX_NAME);
- await("Index did not become available in time")
- .atMost(10, SECONDS)
- .until(() ->
isIndexAvailable(unwrapIgniteImpl(CLUSTER.aliveNode()), INDEX_NAME));
-
- verifyNoNodesHaveAnythingInIndex();
- }
-
- @Test
- @Disabled("https://issues.apache.org/jira/browse/IGNITE-26811")
- void
writeIntentFromTxAbandonedWhileWaitingForTransactionsToFinishShouldNotBeIndexed()
throws Exception {
- createTable(1, 1);
-
- // Both disable write intent switch execution and track when we start
waiting for transactions to finish before index build.
- CompletableFuture<Void> startedWaitForPreIndexTxsToFinish = new
CompletableFuture<>();
- CLUSTER.nodes().forEach(node -> {
- unwrapIgniteImpl(node).dropMessages((recipientId, message) -> {
- if (message instanceof WriteIntentSwitchReplicaRequest) {
- return true;
- }
-
- if (message instanceof
IsNodeFinishedRwTransactionsStartedBeforeRequest) {
- startedWaitForPreIndexTxsToFinish.complete(null);
- }
-
- return false;
- });
- });
-
- // Create and abandon a transaction.
- int txCoordinatorOrdinal = 2;
- Transaction tx =
CLUSTER.node(txCoordinatorOrdinal).transactions().begin();
- insertDataInTransaction(tx, TABLE_NAME, List.of("I0", "I1"), new
Object[]{1, 1});
-
- createIndex(INDEX_NAME);
- assertThat(startedWaitForPreIndexTxsToFinish,
willCompleteSuccessfully());
-
- // The index pre-build wait has started, let's restart the coordinator
to abandon the transaction and abruptly terminate
- // the pre-build wait.
- CLUSTER.restartNode(txCoordinatorOrdinal);
-
- await("Index did not become available in time")
- .atMost(10, SECONDS)
- .until(() ->
isIndexAvailable(unwrapIgniteImpl(CLUSTER.aliveNode()), INDEX_NAME));
-
- verifyNoNodesHaveAnythingInIndex();
- }
-
- private void verifyNoNodesHaveAnythingInIndex() {
- for (int nodeIndex = 0; nodeIndex < initialNodes(); nodeIndex++) {
- IgniteImpl ignite = unwrapIgniteImpl(node(nodeIndex));
-
- CatalogIndexDescriptor indexDescriptor =
indexDescriptor(INDEX_NAME, ignite);
- SortedIndexStorage indexStorage = (SortedIndexStorage)
indexStorage(indexDescriptor, 0, ignite);
-
- if (indexStorage != null) {
- try (Cursor<IndexRow> indexRows =
indexStorage.readOnlyScan(null, null, 0)) {
- assertFalse(indexRows.hasNext(), "Nothing should have been
put to the index, but it was found on node " + nodeIndex);
- }
- }
- }
- }
-
- private static void disableWriteIntentSwitchExecution() {
- CLUSTER.runningNodes().forEach(ignite -> {
- unwrapIgniteImpl(ignite).dropMessages((recipientId, message) ->
message instanceof WriteIntentSwitchReplicaRequest);
- });
- }
-
- private static CatalogIndexDescriptor indexDescriptor(String indexName,
IgniteImpl ignite) {
- return getIndexStrict(ignite.catalogManager(), indexName,
ignite.clock().nowLong());
- }
-
- private static @Nullable IndexStorage indexStorage(CatalogIndexDescriptor
indexDescriptor, int partitionId, IgniteImpl ignite) {
- TableViewInternal tableViewInternal =
tableViewInternal(indexDescriptor.tableId(), ignite);
-
- int indexId = indexDescriptor.id();
-
- IndexStorage indexStorage;
- try {
- indexStorage =
tableViewInternal.internalTable().storage().getIndex(partitionId, indexId);
- } catch (StorageException e) {
- if (e.getMessage().contains("Partition ID " + partitionId + " does
not exist")) {
- return null;
- }
-
- throw e;
- }
-
- assertNotNull(indexStorage, String.format("No index storage exists for
indexId=%s, partitionId=%s", indexId, partitionId));
-
- return indexStorage;
- }
-
- private static TableViewInternal tableViewInternal(int tableId, Ignite
ignite) {
- CompletableFuture<List<Table>> tablesFuture =
ignite.tables().tablesAsync();
-
- assertThat(tablesFuture, willCompleteSuccessfully());
-
- TableViewInternal tableViewInternal = tablesFuture.join().stream()
- .map(TestWrappers::unwrapTableViewInternal)
- .filter(table -> table.tableId() == tableId)
- .findFirst()
- .orElse(null);
-
- assertNotNull(tableViewInternal, "No table object found for tableId="
+ tableId);
-
- return tableViewInternal;
- }
-
@SafeVarargs
private static String toValuesString(List<Object>... values) {
return Stream.of(values)
@@ -374,7 +241,7 @@ public class ItBuildIndexTest extends
BaseSqlIntegrationTest {
));
}
- private void createIndex(String indexName) throws Exception {
+ private void createIndex(String indexName) {
// We execute this operation asynchronously, because some tests block
network messages, which makes the underlying code
// stuck with timeouts. We don't need to wait for the operation to
complete, as we wait for the necessary invariants further
// below.
@@ -394,7 +261,7 @@ public class ItBuildIndexTest extends
BaseSqlIntegrationTest {
*
* @param indexName Name of an index to wait for.
*/
- private static void waitForIndex(String indexName) throws
InterruptedException {
+ private static void waitForIndex(String indexName) {
await().atMost(10, SECONDS).until(
() -> CLUSTER.runningNodes()
.map(TestWrappers::unwrapIgniteImpl)
@@ -432,7 +299,7 @@ public class ItBuildIndexTest extends
BaseSqlIntegrationTest {
};
}
- private static void checkIndexBuild(int partitions, int replicas, String
indexName) throws Exception {
+ private static void checkIndexBuild(int partitions, int replicas, String
indexName) {
Map<Integer, Set<String>> nodesWithBuiltIndexesByPartitionId =
waitForIndexBuild(TABLE_NAME, indexName);
// Check that the number of nodes with built indexes is equal to the
number of replicas.
@@ -550,7 +417,7 @@ public class ItBuildIndexTest extends
BaseSqlIntegrationTest {
* @param node Node.
* @param indexName Index name.
*/
- private static @Nullable CatalogIndexDescriptor
getIndexDescriptor(IgniteImpl node, String indexName) {
+ static @Nullable CatalogIndexDescriptor getIndexDescriptor(IgniteImpl
node, String indexName) {
HybridClock clock = node.clock();
CatalogManager catalogManager = node.catalogManager();
return
catalogManager.activeCatalog(clock.nowLong()).aliveIndex(SCHEMA_NAME,
indexName);
diff --git
a/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexWriteIntentsHandlingTest.java
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexWriteIntentsHandlingTest.java
new file mode 100644
index 00000000000..6eb989d1d62
--- /dev/null
+++
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/ItBuildIndexWriteIntentsHandlingTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.index;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static
org.apache.ignite.internal.ClusterPerClassIntegrationTest.isIndexAvailable;
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_PROFILE;
+import static
org.apache.ignite.internal.index.ItBuildIndexTest.getIndexDescriptor;
+import static
org.apache.ignite.internal.index.WriteIntentSwitchControl.disableWriteIntentSwitchExecution;
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static org.apache.ignite.internal.table.TableTestUtils.getIndexStrict;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
+import org.apache.ignite.internal.TestWrappers;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import
org.apache.ignite.internal.index.message.IsNodeFinishedRwTransactionsStartedBeforeRequest;
+import org.apache.ignite.internal.storage.StorageException;
+import org.apache.ignite.internal.storage.index.IndexRow;
+import org.apache.ignite.internal.storage.index.IndexStorage;
+import org.apache.ignite.internal.storage.index.SortedIndexStorage;
+import org.apache.ignite.internal.table.TableViewInternal;
+import org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicaRequest;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.table.Table;
+import org.apache.ignite.tx.Transaction;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+class ItBuildIndexWriteIntentsHandlingTest extends
ClusterPerTestIntegrationTest {
+ private static final String ZONE_NAME = "ZONE_TABLE";
+
+ private static final String TABLE_NAME = "TEST_TABLE";
+
+ private static final String INDEX_NAME = "TEST_INDEX";
+
+ @Test
+ void writeIntentFromTxAbandonedBeforeShouldNotBeIndexed() {
+ createTable(1, 1);
+
+ disableWriteIntentSwitchExecution(cluster);
+
+ // Create and abandon a transaction.
+ int txCoordinatorOrdinal = 2;
+ Transaction tx =
cluster.node(txCoordinatorOrdinal).transactions().begin();
+ insertDataInTransaction(tx, TABLE_NAME, List.of("I0", "I1"), new
Object[]{1, 1});
+
+ cluster.restartNode(txCoordinatorOrdinal);
+
+ createIndex(INDEX_NAME);
+ await("Index did not become available in time")
+ .atMost(10, SECONDS)
+ .until(() ->
isIndexAvailable(unwrapIgniteImpl(cluster.aliveNode()), INDEX_NAME));
+
+ verifyNoNodesHaveAnythingInIndex();
+ }
+
+ @Test
+ @Disabled("https://issues.apache.org/jira/browse/IGNITE-26811")
+ void
writeIntentFromTxAbandonedWhileWaitingForTransactionsToFinishShouldNotBeIndexed()
{
+ createTable(1, 1);
+
+ // Both disable write intent switch execution and track when we start
waiting for transactions to finish before index build.
+ CompletableFuture<Void> startedWaitForPreIndexTxsToFinish = new
CompletableFuture<>();
+ cluster.nodes().forEach(node -> {
+ unwrapIgniteImpl(node).dropMessages((recipientId, message) -> {
+ if (message instanceof WriteIntentSwitchReplicaRequest) {
+ return true;
+ }
+
+ if (message instanceof
IsNodeFinishedRwTransactionsStartedBeforeRequest) {
+ startedWaitForPreIndexTxsToFinish.complete(null);
+ }
+
+ return false;
+ });
+ });
+
+ // Create and abandon a transaction.
+ int txCoordinatorOrdinal = 2;
+ Transaction tx =
cluster.node(txCoordinatorOrdinal).transactions().begin();
+ insertDataInTransaction(tx, TABLE_NAME, List.of("I0", "I1"), new
Object[]{1, 1});
+
+ createIndex(INDEX_NAME);
+ assertThat(startedWaitForPreIndexTxsToFinish,
willCompleteSuccessfully());
+
+ // The index pre-build wait has started, let's restart the coordinator
to abandon the transaction and abruptly terminate
+ // the pre-build wait.
+ cluster.restartNode(txCoordinatorOrdinal);
+
+ await("Index did not become available in time")
+ .atMost(10, SECONDS)
+ .until(() ->
isIndexAvailable(unwrapIgniteImpl(cluster.aliveNode()), INDEX_NAME));
+
+ verifyNoNodesHaveAnythingInIndex();
+ }
+
+ private void verifyNoNodesHaveAnythingInIndex() {
+ for (int nodeIndex = 0; nodeIndex < initialNodes(); nodeIndex++) {
+ IgniteImpl ignite = unwrapIgniteImpl(node(nodeIndex));
+
+ CatalogIndexDescriptor indexDescriptor =
indexDescriptor(INDEX_NAME, ignite);
+ SortedIndexStorage indexStorage = (SortedIndexStorage)
indexStorage(indexDescriptor, 0, ignite);
+
+ if (indexStorage != null) {
+ try (Cursor<IndexRow> indexRows =
indexStorage.readOnlyScan(null, null, 0)) {
+ assertFalse(indexRows.hasNext(), "Nothing should have been
put to the index, but it was found on node " + nodeIndex);
+ }
+ }
+ }
+ }
+
+ private static CatalogIndexDescriptor indexDescriptor(String indexName,
IgniteImpl ignite) {
+ return getIndexStrict(ignite.catalogManager(), indexName,
ignite.clock().nowLong());
+ }
+
+ private static @Nullable IndexStorage indexStorage(CatalogIndexDescriptor
indexDescriptor, int partitionId, IgniteImpl ignite) {
+ TableViewInternal tableViewInternal =
tableViewInternal(indexDescriptor.tableId(), ignite);
+
+ int indexId = indexDescriptor.id();
+
+ IndexStorage indexStorage;
+ try {
+ indexStorage =
tableViewInternal.internalTable().storage().getIndex(partitionId, indexId);
+ } catch (StorageException e) {
+ if (e.getMessage().contains("Partition ID " + partitionId + " does
not exist")) {
+ return null;
+ }
+
+ throw e;
+ }
+
+ assertNotNull(indexStorage, String.format("No index storage exists for
indexId=%s, partitionId=%s", indexId, partitionId));
+
+ return indexStorage;
+ }
+
+ private static TableViewInternal tableViewInternal(int tableId, Ignite
ignite) {
+ CompletableFuture<List<Table>> tablesFuture =
ignite.tables().tablesAsync();
+
+ assertThat(tablesFuture, willCompleteSuccessfully());
+
+ TableViewInternal tableViewInternal = tablesFuture.join().stream()
+ .map(TestWrappers::unwrapTableViewInternal)
+ .filter(table -> table.tableId() == tableId)
+ .findFirst()
+ .orElse(null);
+
+ assertNotNull(tableViewInternal, "No table object found for tableId="
+ tableId);
+
+ return tableViewInternal;
+ }
+
+ private void createTable(int replicas, int partitions) {
+ IgniteSql sql = cluster.node(0).sql();
+
+ sql.executeScript(format("CREATE ZONE IF NOT EXISTS {} (REPLICAS {},
PARTITIONS {}) STORAGE PROFILES ['{}']",
+ ZONE_NAME, replicas, partitions, DEFAULT_STORAGE_PROFILE
+ ));
+ sql.executeScript(format(
+ "CREATE TABLE {} (i0 INTEGER PRIMARY KEY, i1 INTEGER) ZONE {}",
+ TABLE_NAME, ZONE_NAME
+ ));
+ }
+
+ private void createIndex(String indexName) {
+ // We execute this operation asynchronously, because some tests block
network messages, which makes the underlying code
+ // stuck with timeouts. We don't need to wait for the operation to
complete, as we wait for the necessary invariants further
+ // below.
+ cluster.aliveNode().sql()
+ .executeAsync(null, format("CREATE INDEX {} ON {} (i1)",
indexName, TABLE_NAME))
+ .whenComplete((res, ex) -> {
+ if (ex != null) {
+ log.error("Failed to create index", ex);
+ }
+ });
+
+ waitForIndex(indexName);
+ }
+
+ /**
+ * Waits for all nodes in the cluster to have the given index in the
Catalog.
+ *
+ * @param indexName Name of an index to wait for.
+ */
+ private void waitForIndex(String indexName) {
+ await().atMost(10, SECONDS).until(
+ () -> cluster.runningNodes()
+ .map(TestWrappers::unwrapIgniteImpl)
+ .map(node -> getIndexDescriptor(node, indexName))
+ .allMatch(Objects::nonNull)
+ );
+ }
+
+ private void insertDataInTransaction(Transaction tx, String tblName,
List<String> columnNames, Object[]... tuples) {
+ String insertStmt = "INSERT INTO " + tblName + "(" + String.join(", ",
columnNames) + ")"
+ + " VALUES (" + ", ?".repeat(columnNames.size()).substring(2)
+ ")";
+
+ for (Object[] args : tuples) {
+ cluster.node(0).sql().execute(tx, insertStmt, args).close();
+ }
+ }
+}
diff --git
a/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/WriteIntentSwitchControl.java
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/WriteIntentSwitchControl.java
new file mode 100644
index 00000000000..b5ffe7b3274
--- /dev/null
+++
b/modules/index/src/integrationTest/java/org/apache/ignite/internal/index/WriteIntentSwitchControl.java
@@ -0,0 +1,31 @@
+/*
+ * 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.index;
+
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+
+import org.apache.ignite.internal.Cluster;
+import org.apache.ignite.internal.tx.message.WriteIntentSwitchReplicaRequest;
+
+class WriteIntentSwitchControl {
+ static void disableWriteIntentSwitchExecution(Cluster cluster) {
+ cluster.runningNodes().forEach(ignite -> {
+ unwrapIgniteImpl(ignite).dropMessages((recipientId, message) ->
message instanceof WriteIntentSwitchReplicaRequest);
+ });
+ }
+}
diff --git
a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
b/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
index fdbc4366705..bfdb1dddf85 100644
---
a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
+++
b/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
@@ -657,7 +657,7 @@ public abstract class ClusterPerClassIntegrationTest
extends BaseIgniteAbstractT
* @param ignite Node.
* @param indexName Index name that is being checked.
*/
- protected static boolean isIndexAvailable(IgniteImpl ignite, String
indexName) {
+ public static boolean isIndexAvailable(IgniteImpl ignite, String
indexName) {
CatalogManager catalogManager = ignite.catalogManager();
HybridClock clock = ignite.clock();