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

zstan pushed a commit to branch ignite-3.1.0
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/ignite-3.1.0 by this push:
     new 228572c4181 IGNITE-26100 Add Metastore Raft log compatibility test 
(#6721)
228572c4181 is described below

commit 228572c4181f09a63934112ca0609bcae7db4560
Author: Phillippko <[email protected]>
AuthorDate: Thu Oct 9 17:05:36 2025 +0400

    IGNITE-26100 Add Metastore Raft log compatibility test (#6721)
---
 modules/compatibility-tests/build.gradle           |   4 +
 .../internal/MetastorageRaftCompatibilityTest.java | 134 +++++++++++++++++++++
 .../internal/PersistentCompatibilityTest.java      |  53 +-------
 .../SendAllMetastorageCommandTypesJob.java         |  71 +++++++++++
 .../ignite/internal/CompatibilityTestBase.java     |   9 ++
 .../ignite/internal/CompatibilityTestCommon.java   |  53 ++++++++
 .../org/apache/ignite/internal/IgniteCluster.java  |  91 ++++++++++++--
 .../org/apache/ignite/internal/RunnerNode.java     |  11 +-
 8 files changed, 359 insertions(+), 67 deletions(-)

diff --git a/modules/compatibility-tests/build.gradle 
b/modules/compatibility-tests/build.gradle
index 2928884d00b..8978c90ccb0 100644
--- a/modules/compatibility-tests/build.gradle
+++ b/modules/compatibility-tests/build.gradle
@@ -48,6 +48,8 @@ dependencies {
     integrationTestImplementation project(':ignite-storage-api')
     integrationTestImplementation project(':ignite-storage-page-memory')
     integrationTestImplementation project(':ignite-rest-api')
+    integrationTestImplementation project(':ignite-metastorage-api')
+    integrationTestImplementation project(':ignite-metastorage')
     integrationTestAnnotationProcessor 
libs.micronaut.inject.annotation.processor
 
     integrationTestImplementation libs.micronaut.junit5
@@ -64,6 +66,8 @@ dependencies {
     }
     testFixturesImplementation libs.cytodynamics.nucleus
     testFixturesImplementation libs.japicmp
+    testFixturesImplementation libs.micronaut.http.client
+
 
     testFixturesImplementation testFixtures(project(':ignite-core'))
     testFixturesImplementation testFixtures(project(':ignite-runner'))
diff --git 
a/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/MetastorageRaftCompatibilityTest.java
 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/MetastorageRaftCompatibilityTest.java
new file mode 100644
index 00000000000..d88e2718dbe
--- /dev/null
+++ 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/MetastorageRaftCompatibilityTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import static 
org.apache.ignite.internal.CompatibilityTestCommon.TABLE_NAME_TEST;
+import static 
org.apache.ignite.internal.CompatibilityTestCommon.createDefaultTables;
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static 
org.apache.ignite.internal.lang.IgniteSystemProperties.COLOCATION_FEATURE_FLAG;
+import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.compute.JobDescriptor;
+import org.apache.ignite.compute.JobTarget;
+import org.apache.ignite.deployment.DeploymentUnit;
+import org.apache.ignite.internal.configuration.ComponentWorkingDir;
+import org.apache.ignite.internal.metastorage.MetaStorageManager;
+import org.apache.ignite.internal.testframework.WithSystemProperty;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedClass;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/** Compatibility tests for metastorage raft log. */
+@ParameterizedClass
+@MethodSource("baseVersions")
+@MicronautTest(rebuildContext = true)
+// Old version node starts with disabled colocation. New version nodes that 
start from scratch would fail to join cluster.
+@WithSystemProperty(key = COLOCATION_FEATURE_FLAG, value = "false")
+public class MetastorageRaftCompatibilityTest extends CompatibilityTestBase {
+    @Override
+    protected boolean restartWithCurrentEmbeddedVersion() {
+        return false;
+    }
+
+    @Override
+    protected int nodesCount() {
+        return 1;
+    }
+
+    @Override
+    protected void setupBaseVersion(Ignite baseIgnite) {
+        createDefaultTables(baseIgnite);
+
+        Path nodeWorkDir = cluster.runnerNodeWorkDir(0);
+
+        try {
+            deploySendAllMetastorageCommandTypesJob();
+
+            runSendAllMetastorageCommandTypesJob();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        cluster.stop();
+
+        // To force metastorage recovery from the raft log.
+        deleteMetastorageDbDir(nodeWorkDir);
+    }
+
+    @AfterEach
+    void tearDown()  {
+        cluster.stop();
+    }
+
+    @Test
+    void testReapplication() {
+        cluster.startEmbedded(1, false);
+
+        checkMetastorage();
+    }
+
+    @Test
+    void testStreamToFollower() throws InterruptedException {
+        cluster.startEmbedded(2, false);
+
+        checkMetastorage();
+
+        MetaStorageManager newNodeMetastorage = 
unwrapIgniteImpl(cluster.node(1)).metaStorageManager();
+        MetaStorageManager oldNodeMetastorage = 
unwrapIgniteImpl(cluster.node(0)).metaStorageManager();
+
+        // Assert that new node got all log entries from old one.
+        assertTrue(waitForCondition(() -> oldNodeMetastorage.appliedRevision() 
== newNodeMetastorage.appliedRevision(), 10_000));
+    }
+
+    private void checkMetastorage() {
+        // Will fail if metastorage is corrupted.
+        sql("SELECT * FROM " + TABLE_NAME_TEST);
+    }
+
+    private static void deleteMetastorageDbDir(Path nodeWorkDir) {
+        Path metastorageDbDir = new 
ComponentWorkingDir(nodeWorkDir.resolve("metastorage")).dbPath();
+
+        // There is no IgniteUtils.delete() method yet.
+        assertTrue(Files.exists(metastorageDbDir));
+        assertTrue(IgniteUtils.deleteIfExists(metastorageDbDir));
+    }
+
+    private <T, R> void deploySendAllMetastorageCommandTypesJob() throws 
IOException {
+        
CompatibilityTestCommon.deployJob(SendAllMetastorageCommandTypesJob.class, 
workDir, deploymentClient);
+    }
+
+    private void runSendAllMetastorageCommandTypesJob() {
+        try (IgniteClient client = cluster.createClient()) {
+            JobDescriptor<String, Void> job = 
JobDescriptor.builder(SendAllMetastorageCommandTypesJob.class)
+                    .units(new 
DeploymentUnit(SendAllMetastorageCommandTypesJob.class.getName(), 
"1.0.0")).build();
+
+            JobTarget jobTarget = JobTarget.anyNode(client.cluster().nodes());
+
+            client.compute().execute(jobTarget, job, "");
+        }
+    }
+}
diff --git 
a/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/PersistentCompatibilityTest.java
 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/PersistentCompatibilityTest.java
index c6c138491e5..1c790cb96c4 100644
--- 
a/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/PersistentCompatibilityTest.java
+++ 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/PersistentCompatibilityTest.java
@@ -20,25 +20,9 @@ package org.apache.ignite.internal;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 
-import io.micronaut.http.HttpRequest;
-import io.micronaut.http.HttpResponse;
-import io.micronaut.http.HttpStatus;
-import io.micronaut.http.MediaType;
-import io.micronaut.http.MutableHttpRequest;
-import io.micronaut.http.client.HttpClient;
-import io.micronaut.http.client.annotation.Client;
-import io.micronaut.http.client.multipart.MultipartBody;
-import io.micronaut.http.client.multipart.MultipartBody.Builder;
 import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
-import jakarta.inject.Inject;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.List;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.client.IgniteClient;
 import org.apache.ignite.compute.JobDescriptor;
@@ -87,8 +71,6 @@ import org.junit.jupiter.params.provider.ValueSource;
 // PersistentPageMemoryStorageEngine
 @WithSystemProperty(key = IgniteSystemProperties.THREAD_ASSERTIONS_ENABLED, 
value = "false")
 public class PersistentCompatibilityTest extends CompatibilityTestBase {
-    private static final String NODE_URL = "http://localhost:"; + 
ClusterConfiguration.DEFAULT_BASE_HTTP_PORT;
-
     /** Delta files are not compacted before updating the cluster. */
     private static final String TABLE_WITH_DELTA_FILES = 
"TEST_WITH_DELTA_FILES";
 
@@ -107,10 +89,6 @@ public class PersistentCompatibilityTest extends 
CompatibilityTestBase {
     private static final String ORIGINAL_ROW_VALUE = "original_value";
     private static final String UPDATED_ROW_VALUE = "updated_value";
 
-    @Inject
-    @Client(NODE_URL + "/management/v1/deployment")
-    private HttpClient deploymentClient;
-
     @Override
     protected int nodesCount() {
         return 1;
@@ -207,36 +185,7 @@ public class PersistentCompatibilityTest extends 
CompatibilityTestBase {
     }
 
     private <T, R> void deployCheckpointJob() throws IOException {
-        Path jarFile = createJar(CheckpointJob.class);
-
-        HttpResponse<Object> deploy = deploy(CheckpointJob.class.getName(), 
"1.0.0", jarFile.toFile());
-        assertThat(deploy.status(), is(HttpStatus.OK));
-    }
-
-    private Path createJar(Class<?> clazz) throws IOException {
-        String resource = clazz.getName().replace('.', '/') + ".class";
-        Path path = 
Path.of(clazz.getClassLoader().getResource(resource).getPath());
-        Path jarFile = Files.createFile(workDir.resolve("CheckpointJob.jar"));
-
-        try (FileOutputStream fos = new FileOutputStream(jarFile.toFile()); 
JarOutputStream jos = new JarOutputStream(fos)) {
-            JarEntry entry = new JarEntry(resource);
-            jos.putNextEntry(entry);
-            Files.copy(path, jos);
-            jos.closeEntry();
-        }
-
-        return jarFile;
-    }
-
-    private HttpResponse<Object> deploy(String id, String version, File file) {
-        Builder builder = MultipartBody.builder();
-        builder.addPart("unitContent", file);
-        MultipartBody body = builder.build();
-
-        MutableHttpRequest<MultipartBody> post = HttpRequest.POST("units/" + 
id + "/" + version, body)
-                .contentType(MediaType.MULTIPART_FORM_DATA);
-
-        return deploymentClient.toBlocking().exchange(post);
+        CompatibilityTestCommon.deployJob(CheckpointJob.class, workDir, 
deploymentClient);
     }
 
     private static void insertRow(Ignite baseIgnite, String tableName, int id, 
String name) {
diff --git 
a/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/SendAllMetastorageCommandTypesJob.java
 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/SendAllMetastorageCommandTypesJob.java
new file mode 100644
index 00000000000..e2c255990b1
--- /dev/null
+++ 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/SendAllMetastorageCommandTypesJob.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import static java.util.concurrent.CompletableFuture.allOf;
+import static org.apache.ignite.internal.metastorage.dsl.Conditions.exists;
+import static org.apache.ignite.internal.metastorage.dsl.Operations.noop;
+import static org.apache.ignite.internal.metastorage.dsl.Operations.ops;
+import static org.apache.ignite.internal.metastorage.dsl.Statements.iif;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.compute.ComputeJob;
+import org.apache.ignite.compute.JobExecutionContext;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
+import org.apache.ignite.internal.lang.ByteArray;
+import org.apache.ignite.internal.metastorage.impl.MetaStorageManagerImpl;
+import org.apache.ignite.internal.wrapper.Wrappers;
+
+/** A job that runs different MetastorageWriteCommands. */
+class SendAllMetastorageCommandTypesJob implements ComputeJob<String, Void> {
+    @Override
+    public CompletableFuture<Void> executeAsync(JobExecutionContext context, 
String arg) {
+        IgniteImpl igniteImpl = Wrappers.unwrap(context.ignite(), 
IgniteImpl.class);
+
+        try {
+            byte[] value = "value".getBytes();
+
+            MetaStorageManagerImpl metastorage = (MetaStorageManagerImpl) 
igniteImpl.metaStorageManager();
+
+            return allOf(
+                    metastorage.put(ByteArray.fromString("put"), value),
+                    metastorage.putAll(Map.of(ByteArray.fromString("putAll"), 
value)),
+                    metastorage.remove(ByteArray.fromString("remove")),
+                    
metastorage.removeAll(Set.of(ByteArray.fromString("removeAll"))),
+                    
metastorage.removeByPrefix(ByteArray.fromString("removeByPrefix")),
+                    metastorage.invoke(exists(ByteArray.fromString("key")), 
noop(), noop()),
+                    
metastorage.invoke(iif(exists(ByteArray.fromString("key")), ops().yield(), 
ops().yield())),
+                    
metastorage.evictIdempotentCommandsCache(HybridTimestamp.MAX_VALUE),
+                    sendCompactionCommand(metastorage)
+            );
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static CompletableFuture<Void> 
sendCompactionCommand(MetaStorageManagerImpl metastorage)
+            throws Exception {
+        Method sendCompactionCommand = 
metastorage.getClass().getDeclaredMethod("sendCompactionCommand", long.class);
+        sendCompactionCommand.setAccessible(true);
+        return (CompletableFuture<Void>) 
sendCompactionCommand.invoke(metastorage, metastorage.appliedRevision());
+    }
+}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
index 7c70867fc8b..b260d82b092 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
@@ -25,6 +25,9 @@ import static 
org.apache.ignite.internal.testframework.flow.TestFlowUtils.subscr
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
 import static org.awaitility.Awaitility.await;
 
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import jakarta.inject.Inject;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collections;
@@ -81,6 +84,8 @@ public abstract class CompatibilityTestBase extends 
BaseIgniteAbstractTest {
             + "  }\n"
             + "}";
 
+    private static final String NODE_URL = "http://localhost:"; + 
ClusterConfiguration.DEFAULT_BASE_HTTP_PORT;
+
     // If there are no fields annotated with @Parameter, constructor injection 
will be used, which is incompatible with the
     // Lifecycle.PER_CLASS.
     @SuppressWarnings("unused")
@@ -97,6 +102,10 @@ public abstract class CompatibilityTestBase extends 
BaseIgniteAbstractTest {
         return Collections.emptyList();
     }
 
+    @Inject
+    @Client(NODE_URL + "/management/v1/deployment")
+    protected HttpClient deploymentClient;
+
     @SuppressWarnings("unused")
     @BeforeParameterizedClassInvocation
     void startCluster(String baseVersion, TestInfo testInfo) {
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestCommon.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestCommon.java
index 69b8e9b1825..f5c10891cc1 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestCommon.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestCommon.java
@@ -17,9 +17,27 @@
 
 package org.apache.ignite.internal;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.HttpStatus;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.MutableHttpRequest;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.multipart.MultipartBody;
+import io.micronaut.http.client.multipart.MultipartBody.Builder;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.sql.SqlRow;
 import org.jetbrains.annotations.Nullable;
@@ -85,4 +103,39 @@ public class CompatibilityTestCommon {
             return cursor.wasApplied();
         }
     }
+
+    /** Deploys class to the cluster using the given deployment client. */
+    public static void deployJob(Class<?> clazz, Path workDir, HttpClient 
deploymentClient) throws IOException {
+        Path jarFile = createJar(clazz, workDir);
+
+        HttpResponse<Object> deploy = deploy(clazz.getName(), "1.0.0", 
jarFile.toFile(), deploymentClient);
+        assertThat(deploy.status(), is(HttpStatus.OK));
+
+    }
+
+    private static Path createJar(Class<?> clazz, Path workDir) throws 
IOException {
+        String resource = clazz.getName().replace('.', '/') + ".class";
+        Path path = 
Path.of(clazz.getClassLoader().getResource(resource).getPath());
+        Path jarFile = Files.createFile(workDir.resolve(clazz.getName() + 
".jar"));
+
+        try (FileOutputStream fos = new FileOutputStream(jarFile.toFile()); 
JarOutputStream jos = new JarOutputStream(fos)) {
+            JarEntry entry = new JarEntry(resource);
+            jos.putNextEntry(entry);
+            Files.copy(path, jos);
+            jos.closeEntry();
+        }
+
+        return jarFile;
+    }
+
+    private static HttpResponse<Object> deploy(String id, String version, File 
file, HttpClient deploymentClient) {
+        Builder builder = MultipartBody.builder();
+        builder.addPart("unitContent", file);
+        MultipartBody body = builder.build();
+
+        MutableHttpRequest<MultipartBody> post = HttpRequest.POST("units/" + 
id + "/" + version, body)
+                .contentType(MediaType.MULTIPART_FORM_DATA);
+
+        return deploymentClient.toBlocking().exchange(post);
+    }
 }
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteCluster.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteCluster.java
index 0f46446a02e..594ea267cf9 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteCluster.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteCluster.java
@@ -18,9 +18,13 @@
 package org.apache.ignite.internal;
 
 import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.internal.ClusterConfiguration.configOverrides;
+import static 
org.apache.ignite.internal.ClusterConfiguration.containsOverrides;
 import static org.apache.ignite.internal.Dependencies.constructArgFile;
 import static org.apache.ignite.internal.Dependencies.getProjectRoot;
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static 
org.apache.ignite.internal.testframework.matchers.HttpResponseMatcher.hasStatusCode;
 import static org.apache.ignite.internal.util.CollectionUtils.setListAtIndex;
@@ -40,6 +44,7 @@ import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpRequest.Builder;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -51,7 +56,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteServer;
 import org.apache.ignite.InitParameters;
@@ -65,6 +70,8 @@ import 
org.apache.ignite.internal.testframework.TestIgnitionManager;
 import org.gradle.tooling.GradleConnector;
 import org.gradle.tooling.ProjectConnection;
 import org.gradle.tooling.model.build.BuildEnvironment;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.TestInfo;
 
 /**
  * Cluster of nodes. Can be started with nodes of previous Ignite versions 
running in the external processes or in the embedded mode
@@ -114,6 +121,15 @@ public class IgniteCluster {
      * @param nodesCount Number of nodes in the cluster.
      */
     public void startEmbedded(int nodesCount, boolean initCluster) {
+        startEmbedded(null, nodesCount, initCluster);
+    }
+
+    /**
+     * Starts cluster in embedded mode with nodes of current version.
+     *
+     * @param nodesCount Number of nodes in the cluster.
+     */
+    public void startEmbedded(@Nullable TestInfo testInfo, int nodesCount, 
boolean initCluster) {
         if (started) {
             throw new IllegalStateException("The cluster is already started");
         }
@@ -123,7 +139,7 @@ public class IgniteCluster {
 
         List<ServerRegistration> nodeRegistrations = new ArrayList<>();
         for (int nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) {
-            nodeRegistrations.add(startEmbeddedNode(nodeIndex));
+            nodeRegistrations.add(startEmbeddedNode(testInfo, nodeIndex, 
nodesCount));
         }
 
         if (initCluster) {
@@ -269,14 +285,27 @@ public class IgniteCluster {
         return 
clusterConfiguration.nodeNamingStrategy().nodeName(clusterConfiguration, 
nodeIndex);
     }
 
-    private ServerRegistration startEmbeddedNode(int nodeIndex) {
+    private ServerRegistration startEmbeddedNode(
+            @Nullable TestInfo testInfo,
+            int nodeIndex,
+            int nodesCount
+    ) {
         String nodeName = nodeName(nodeIndex);
+        String config = formatConfig(clusterConfiguration, nodeName, 
nodeIndex, nodesCount);
 
-        IgniteServer node = TestIgnitionManager.start(
+        if (testInfo != null && containsOverrides(testInfo, nodeIndex)) {
+            config = TestIgnitionManager.applyOverridesToConfig(config, 
configOverrides(testInfo, nodeIndex));
+        }
+
+        IgniteServer node = 
clusterConfiguration.usePreConfiguredStorageProfiles()
+                ? TestIgnitionManager.start(
                 nodeName,
-                null,
-                
clusterConfiguration.workDir().resolve(clusterConfiguration.clusterName()).resolve(nodeName)
-        );
+                config,
+                
clusterConfiguration.workDir().resolve(clusterConfiguration.clusterName()).resolve(nodeName))
+                : TestIgnitionManager.startWithoutPreConfiguredStorageProfiles(
+                        nodeName,
+                        config,
+                        
clusterConfiguration.workDir().resolve(clusterConfiguration.clusterName()).resolve(nodeName));
 
         synchronized (igniteServers) {
             setListAtIndex(igniteServers, nodeIndex, node);
@@ -311,13 +340,16 @@ public class IgniteCluster {
 
             String dependenciesListNotation = dependencyIds.stream()
                     .map(dependency -> dependency + ":" + igniteVersion)
-                    .collect(Collectors.joining(","));
+                    .collect(joining(","));
 
             File argFile = constructArgFile(connection, 
dependenciesListNotation, false);
 
             List<RunnerNode> result = new ArrayList<>();
             for (int nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) {
-                result.add(RunnerNode.startNode(javaHome, argFile, 
igniteVersion, clusterConfiguration, nodesCount, nodeIndex));
+                String nodeName = 
clusterConfiguration.nodeNamingStrategy().nodeName(clusterConfiguration, 
nodeIndex);
+                String nodeConfig = formatConfig(clusterConfiguration, 
nodeName, nodeIndex, nodesCount);
+
+                result.add(RunnerNode.startNode(javaHome, argFile, 
igniteVersion, clusterConfiguration, nodeConfig, nodesCount, nodeName));
             }
 
             return result;
@@ -353,4 +385,45 @@ public class IgniteCluster {
     public String clusterName() {
         return clusterConfiguration.clusterName();
     }
+
+    /** Returns list of runner nodes. */
+    public List<RunnerNode> getRunnerNodes() {
+        return runnerNodes;
+    }
+
+    /** Returns embedded node's work directory. */
+    public Path embeddedNodeWorkDir(int nodeIndex) {
+        return workDir(nodeIndex, true);
+    }
+
+    /** Returns runner node's work directory. */
+    public Path runnerNodeWorkDir(int nodeIndex) {
+        return workDir(nodeIndex, false);
+    }
+
+    private Path workDir(int nodeIndex, boolean embedded) {
+        String nodeName = embedded ? igniteServers.get(nodeIndex).name() : 
runnerNodes.get(nodeIndex).nodeName();
+
+        return 
clusterConfiguration.workDir().resolve(clusterConfiguration.clusterName()).resolve(nodeName);
+    }
+
+    private static String seedAddressesString(ClusterConfiguration 
clusterConfiguration, int seedsCount) {
+        return IntStream.range(0, seedsCount)
+                .map(nodeIndex -> clusterConfiguration.basePort() + nodeIndex)
+                .mapToObj(port -> "\"localhost:" + port + '\"')
+                .collect(joining(", "));
+    }
+
+    private static String formatConfig(ClusterConfiguration 
clusterConfiguration, String nodeName, int nodeIndex, int nodesCount) {
+        return format(
+                clusterConfiguration.defaultNodeBootstrapConfigTemplate(),
+                clusterConfiguration.basePort() + nodeIndex,
+                seedAddressesString(clusterConfiguration, nodesCount),
+                clusterConfiguration.baseClientPort() + nodeIndex,
+                clusterConfiguration.baseHttpPort() + nodeIndex,
+                clusterConfiguration.baseHttpsPort() + nodeIndex,
+                nodeName,
+                nodeIndex
+        );
+    }
 }
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
index 6efa40e8541..e8935c05d2b 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
@@ -67,7 +67,7 @@ public class RunnerNode {
      * @param igniteVersion Version of the Ignite. Used to get the 
configuration defaults.
      * @param clusterConfiguration Test cluster configuration.
      * @param nodesCount Overall number of nodes.
-     * @param nodeIndex Current node index.
+     * @param nodeName Node name.
      * @return Instance of the control object.
      * @throws IOException If an I/O exception occurs.
      */
@@ -76,12 +76,11 @@ public class RunnerNode {
             File argFile,
             String igniteVersion,
             ClusterConfiguration clusterConfiguration,
+            String nodeConfig,
             int nodesCount,
-            int nodeIndex
+            String nodeName
     ) throws IOException {
-        String nodeName = 
clusterConfiguration.nodeNamingStrategy().nodeName(clusterConfiguration, 
nodeIndex);
         Path workDir = 
clusterConfiguration.workDir().resolve(clusterConfiguration.clusterName()).resolve(nodeName);
-        String configStr = formatConfig(clusterConfiguration, nodeName, 
nodeIndex, nodesCount);
 
         Files.createDirectories(workDir);
         Path configPath = workDir.resolve(DEFAULT_CONFIG_NAME);
@@ -91,13 +90,13 @@ public class RunnerNode {
             Map<String, String> defaultsPerVersion = 
DEFAULTS_PER_VERSION.get(igniteVersion);
             Map<String, String> storageProfilesPerVersion = 
STORAGE_PROFILES_PER_VERSION.get(igniteVersion);
             writeConfigurationFileApplyingTestDefaults(
-                    configStr,
+                    nodeConfig,
                     configPath,
                     defaultsPerVersion != null ? defaultsPerVersion : DEFAULTS,
                     storageProfilesPerVersion != null ? 
storageProfilesPerVersion : STORAGE_PROFILES
             );
         } else {
-            writeConfigurationFile(configStr, configPath);
+            writeConfigurationFile(nodeConfig, configPath);
         }
 
         Process process = executeRunner(javaHome, argFile, configPath, 
workDir, nodeName);

Reply via email to