Vladsz83 commented on code in PR #13108:
URL: https://github.com/apache/ignite/pull/13108#discussion_r3276468408


##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */

Review Comment:
   Let's describe what it does.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */

Review Comment:
   Use full stops pls.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";

Review Comment:
   Is there any automation? Do we need to fix this version at each release?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";
+
+    /** Resolves the Target distribution. */
+    public static File resolveTargetDist() {
+        String explicit = IgniteSystemProperties.getString(TARGET_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cur = new File(System.getProperty("user.dir"));
+
+        while (cur != null) {
+            File potential = new File(cur, DEFAULT_TARGET_REL_PATH);
+
+            if (potential.exists() && potential.isDirectory())
+                return potential;
+
+            cur = cur.getParentFile();
+        }
+
+        throw new IllegalStateException(
+            "Target distribution not found. " +
+            "Build with '-Prelease' or set -D" + TARGET_DIST
+        );
+    }
+
+    /** Resolves the Base distribution. */
+    public static File resolveBaseDist() {
+        String explicit = IgniteSystemProperties.getString(BASE_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cacheDir = getCacheDir();
+        File versionedDir = new File(cacheDir, "ignite-" + LATEST_VERSION);
+
+        if (!versionedDir.exists())
+            downloadAndUnpack(LATEST_VERSION, versionedDir);
+
+        return versionedDir;
+    }
+
+    /** */
+    public static File getCacheDir() {
+        String tmpDir = Paths.get(System.getProperty("user.dir"), 
"/target/upgrade-test/ignite-releases").toString();
+        String path = IgniteSystemProperties.getString(RELEASES_DIR, tmpDir);
+
+        assert path != null : "Ignite releases directory is null!";
+
+        File dir = new File(path);
+
+        if (!dir.exists())

Review Comment:
   !dir.exists() && !dir.mkdirs() ?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";
+
+    /** Resolves the Target distribution. */
+    public static File resolveTargetDist() {
+        String explicit = IgniteSystemProperties.getString(TARGET_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cur = new File(System.getProperty("user.dir"));
+
+        while (cur != null) {
+            File potential = new File(cur, DEFAULT_TARGET_REL_PATH);
+
+            if (potential.exists() && potential.isDirectory())
+                return potential;
+
+            cur = cur.getParentFile();
+        }
+
+        throw new IllegalStateException(
+            "Target distribution not found. " +
+            "Build with '-Prelease' or set -D" + TARGET_DIST
+        );
+    }
+
+    /** Resolves the Base distribution. */
+    public static File resolveBaseDist() {
+        String explicit = IgniteSystemProperties.getString(BASE_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cacheDir = getCacheDir();
+        File versionedDir = new File(cacheDir, "ignite-" + LATEST_VERSION);
+
+        if (!versionedDir.exists())
+            downloadAndUnpack(LATEST_VERSION, versionedDir);
+
+        return versionedDir;
+    }
+
+    /** */
+    public static File getCacheDir() {
+        String tmpDir = Paths.get(System.getProperty("user.dir"), 
"/target/upgrade-test/ignite-releases").toString();

Review Comment:
   Let's use a constant for the default path. Same beloew.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";
+
+    /** Resolves the Target distribution. */
+    public static File resolveTargetDist() {
+        String explicit = IgniteSystemProperties.getString(TARGET_DIST);
+
+        if (explicit != null && !explicit.isEmpty())

Review Comment:
   F.isEmpty()?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";
+
+    /** Resolves the Target distribution. */
+    public static File resolveTargetDist() {
+        String explicit = IgniteSystemProperties.getString(TARGET_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cur = new File(System.getProperty("user.dir"));
+
+        while (cur != null) {
+            File potential = new File(cur, DEFAULT_TARGET_REL_PATH);
+
+            if (potential.exists() && potential.isDirectory())
+                return potential;
+
+            cur = cur.getParentFile();
+        }
+
+        throw new IllegalStateException(
+            "Target distribution not found. " +

Review Comment:
   No chance for single line?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteConfigGenerator.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import org.apache.ignite.configuration.IgniteConfiguration;
+
+/** */
+public class IgniteConfigGenerator {
+    /** */
+    private final Configuration cfg;
+
+    /** */
+    private final Transformer transformer;
+
+    /** */
+    public IgniteConfigGenerator() {
+        this.cfg = getFreemarketConfiguration();
+        this.transformer = getXmlTransformer();
+    }
+
+    /** */
+    private Configuration getFreemarketConfiguration() {

Review Comment:
   Freemarker ? Also, we normally don't use 'get/is' notations in our internals 
( _getFreemarkerConfiguration_ -> _freemarkerConfiguration_ ). Same everywhere.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteConfigGenerator.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import org.apache.ignite.configuration.IgniteConfiguration;
+
+/** */
+public class IgniteConfigGenerator {
+    /** */
+    private final Configuration cfg;
+
+    /** */
+    private final Transformer transformer;
+
+    /** */
+    public IgniteConfigGenerator() {
+        this.cfg = getFreemarketConfiguration();
+        this.transformer = getXmlTransformer();
+    }
+
+    /** */
+    private Configuration getFreemarketConfiguration() {
+        Configuration cfg0 = new Configuration(Configuration.VERSION_2_3_34);
+
+        cfg0.setClassForTemplateLoading(this.getClass(), "/templates");
+
+        DefaultObjectWrapper wrapper = new 
DefaultObjectWrapper(Configuration.VERSION_2_3_34);
+        wrapper.setExposeFields(true);
+
+        cfg0.setObjectWrapper(wrapper);
+        cfg0.setDefaultEncoding("UTF-8");
+        
cfg0.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+
+        return cfg0;
+    }
+
+    /** */
+    private Transformer getXmlTransformer() {
+        Transformer transformer0;
+
+        try {
+            TransformerFactory factory = TransformerFactory.newInstance();
+
+            factory.setAttribute("indent-number", 4);
+
+            transformer0 = factory.newTransformer();
+        }
+        catch (Exception e) {
+            throw new RuntimeException("Failed to initialize XML transformer", 
e);
+        }
+
+        transformer0.setOutputProperty(OutputKeys.INDENT, "yes");
+        transformer0.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+        transformer0.setOutputProperty(OutputKeys.METHOD, "xml");
+
+        return transformer0;
+    }
+
+    /**
+     * Generates Ignite configuration at a specific, deterministic path.
+     *
+     * @param igniteCfg Ignite configuration object.
+     * @param logDir Directory for logs.
+     * @param cfgPath The base directory path where the "config.xml" should be 
created.

Review Comment:
   "ignite.xml" ?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteRebalanceOnUpgradeTest.java:
##########
@@ -0,0 +1,96 @@
+/*
+ * 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.compatibility.upgrade;
+
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Smoke test for rolling upgrade with persistence.
+ */
+@RunWith(Parameterized.class)
+public class IgniteRebalanceOnUpgradeTest extends IgniteUpgradeAbstractTest {
+    /** Cache name. */
+    private static final String CACHE_NAME = "transactional-cache";
+
+    /** */
+    @Parameterized.Parameter
+    public boolean persistentEnabled;

Review Comment:
   'boolean persistence;' is common practice.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteConfigGenerator.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import org.apache.ignite.configuration.IgniteConfiguration;
+
+/** */
+public class IgniteConfigGenerator {
+    /** */
+    private final Configuration cfg;
+
+    /** */
+    private final Transformer transformer;
+
+    /** */
+    public IgniteConfigGenerator() {
+        this.cfg = getFreemarketConfiguration();
+        this.transformer = getXmlTransformer();
+    }
+
+    /** */
+    private Configuration getFreemarketConfiguration() {
+        Configuration cfg0 = new Configuration(Configuration.VERSION_2_3_34);
+
+        cfg0.setClassForTemplateLoading(this.getClass(), "/templates");
+
+        DefaultObjectWrapper wrapper = new 
DefaultObjectWrapper(Configuration.VERSION_2_3_34);
+        wrapper.setExposeFields(true);
+
+        cfg0.setObjectWrapper(wrapper);
+        cfg0.setDefaultEncoding("UTF-8");
+        
cfg0.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+
+        return cfg0;
+    }
+
+    /** */
+    private Transformer getXmlTransformer() {
+        Transformer transformer0;
+
+        try {
+            TransformerFactory factory = TransformerFactory.newInstance();
+
+            factory.setAttribute("indent-number", 4);
+
+            transformer0 = factory.newTransformer();
+        }
+        catch (Exception e) {
+            throw new RuntimeException("Failed to initialize XML transformer", 
e);

Review Comment:
   Can we just declare exception on this method?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteRebalanceOnUpgradeTest.java:
##########
@@ -0,0 +1,96 @@
+/*
+ * 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.compatibility.upgrade;
+
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Smoke test for rolling upgrade with persistence.
+ */
+@RunWith(Parameterized.class)
+public class IgniteRebalanceOnUpgradeTest extends IgniteUpgradeAbstractTest {
+    /** Cache name. */
+    private static final String CACHE_NAME = "transactional-cache";
+
+    /** */
+    @Parameterized.Parameter
+    public boolean persistentEnabled;
+
+    /** */
+    @Parameterized.Parameters(name = "persistentEnabled={0}")

Review Comment:
   'persistence={0}' is common practice.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteUpgradeAbstractTest.java:
##########
@@ -0,0 +1,292 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.startup.cmdline.CommandLineStartup;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveBaseDist;
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveTargetDist;
+
+/** */
+public abstract class IgniteUpgradeAbstractTest extends GridCommonAbstractTest 
{
+    /** Base Ignite Work Directory. */
+    public static final String WORK_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR);
+
+    /** Base Ignite Logging Directory. */
+    public static final String LOG_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_LOG_DIR);
+
+    /** Base Ignite Config Directory. */
+    public static final String CONFIG_DIR = 
System.getProperty("IGNITE_CONFIG_DIR");
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_WORK_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/work";
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_LOG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/logs";
+
+    /** Default Ignite Config Directory. */
+    private static final String DEFAULT_CONFIG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/config";
+
+    /** Proxies for Multi-JVM nodes. */
+    private final ConcurrentMap<Integer, IgniteProcessProxy> gridProxies = new 
ConcurrentHashMap<>();
+
+    /** Configuration generator. */
+    private final IgniteConfigGenerator cfgGenerator = new 
IgniteConfigGenerator();
+
+    /** Local client instance. */
+    private IgniteEx locJvmInstance;
+
+    /** {@inheritDoc} */
+    @Override protected boolean isMultiJvm() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        deleteRootDir(WORK_DIR != null ? WORK_DIR : DEFAULT_WORK_DIR);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        for (int i : gridProxies.keySet())
+            stopNode(i);

Review Comment:
   Will it coninue if curent node failed for some reason?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";
+
+    /** Resolves the Target distribution. */
+    public static File resolveTargetDist() {
+        String explicit = IgniteSystemProperties.getString(TARGET_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cur = new File(System.getProperty("user.dir"));
+
+        while (cur != null) {
+            File potential = new File(cur, DEFAULT_TARGET_REL_PATH);
+
+            if (potential.exists() && potential.isDirectory())
+                return potential;
+
+            cur = cur.getParentFile();
+        }
+
+        throw new IllegalStateException(
+            "Target distribution not found. " +
+            "Build with '-Prelease' or set -D" + TARGET_DIST
+        );
+    }
+
+    /** Resolves the Base distribution. */
+    public static File resolveBaseDist() {
+        String explicit = IgniteSystemProperties.getString(BASE_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cacheDir = getCacheDir();
+        File versionedDir = new File(cacheDir, "ignite-" + LATEST_VERSION);
+
+        if (!versionedDir.exists())
+            downloadAndUnpack(LATEST_VERSION, versionedDir);
+
+        return versionedDir;
+    }
+
+    /** */
+    public static File getCacheDir() {
+        String tmpDir = Paths.get(System.getProperty("user.dir"), 
"/target/upgrade-test/ignite-releases").toString();
+        String path = IgniteSystemProperties.getString(RELEASES_DIR, tmpDir);
+
+        assert path != null : "Ignite releases directory is null!";
+
+        File dir = new File(path);
+
+        if (!dir.exists())
+            if (!dir.mkdirs())
+                throw new IllegalStateException("Failed to create directory: " 
+ dir.getAbsolutePath());
+
+        return dir;
+    }
+
+    /** Downloads and unpacks Ignite distribution from Apache Archives. */
+    private static void downloadAndUnpack(String ver, File dest) {
+        String zipName = "apache-ignite-" + ver + "-bin.zip";
+        String downloadUrl = "https://www.apache.org/dyn/closer.lua/ignite/"; + 
ver + "/" + zipName + "?action=download";

Review Comment:
   Same here: let's use some constants.



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteConfigGenerator.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import org.apache.ignite.configuration.IgniteConfiguration;
+
+/** */
+public class IgniteConfigGenerator {
+    /** */
+    private final Configuration cfg;
+
+    /** */
+    private final Transformer transformer;
+
+    /** */
+    public IgniteConfigGenerator() {
+        this.cfg = getFreemarketConfiguration();
+        this.transformer = getXmlTransformer();
+    }
+
+    /** */
+    private Configuration getFreemarketConfiguration() {
+        Configuration cfg0 = new Configuration(Configuration.VERSION_2_3_34);
+
+        cfg0.setClassForTemplateLoading(this.getClass(), "/templates");
+
+        DefaultObjectWrapper wrapper = new 
DefaultObjectWrapper(Configuration.VERSION_2_3_34);
+        wrapper.setExposeFields(true);
+
+        cfg0.setObjectWrapper(wrapper);
+        cfg0.setDefaultEncoding("UTF-8");
+        
cfg0.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+
+        return cfg0;
+    }
+
+    /** */
+    private Transformer getXmlTransformer() {
+        Transformer transformer0;
+
+        try {
+            TransformerFactory factory = TransformerFactory.newInstance();
+
+            factory.setAttribute("indent-number", 4);
+
+            transformer0 = factory.newTransformer();
+        }
+        catch (Exception e) {
+            throw new RuntimeException("Failed to initialize XML transformer", 
e);
+        }
+
+        transformer0.setOutputProperty(OutputKeys.INDENT, "yes");
+        transformer0.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+        transformer0.setOutputProperty(OutputKeys.METHOD, "xml");
+
+        return transformer0;
+    }
+
+    /**
+     * Generates Ignite configuration at a specific, deterministic path.
+     *
+     * @param igniteCfg Ignite configuration object.
+     * @param logDir Directory for logs.
+     * @param cfgPath The base directory path where the "config.xml" should be 
created.
+     * @return The absolute {@link Path} to the generated "config.xml" file.
+     */
+    public Path generateIgniteConfigurationXml(
+        IgniteConfiguration igniteCfg,
+        String logDir,
+        String cfgPath
+    ) throws Exception {
+        Path targetPath = Paths.get(cfgPath + 
"/ignite-config.xml").toAbsolutePath();
+
+        Files.createDirectories(targetPath.getParent());
+
+        Path logXmlPath = generateLog4j2Xml(logDir, cfgPath);
+
+        Map<String, Object> model = new HashMap<>();
+        model.put("config", igniteCfg);
+        model.put("logConfigPath", logXmlPath.toAbsolutePath().toString());
+
+        Template igniteTemp = cfg.getTemplate("ignite.ftl");
+
+        StringWriter sw = new StringWriter();
+        igniteTemp.process(model, sw);
+        String flatXml = sw.toString().replaceAll("(?s)>\\s+<", "><").trim();
+
+        try (Writer out = Files.newBufferedWriter(targetPath)) {
+            transformer.transform(
+                new StreamSource(new StringReader(flatXml)),
+                new StreamResult(out)
+            );
+        }
+
+        return targetPath;
+    }
+
+    /** */
+    public Path generateLog4j2Xml(String logDir, String cfgPath) throws 
Exception {

Review Comment:
   Naming suggestion: 'Log4jConfiguration'



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteUpgradeAbstractTest.java:
##########
@@ -0,0 +1,292 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.startup.cmdline.CommandLineStartup;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveBaseDist;
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveTargetDist;
+
+/** */
+public abstract class IgniteUpgradeAbstractTest extends GridCommonAbstractTest 
{
+    /** Base Ignite Work Directory. */
+    public static final String WORK_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR);
+
+    /** Base Ignite Logging Directory. */
+    public static final String LOG_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_LOG_DIR);
+
+    /** Base Ignite Config Directory. */
+    public static final String CONFIG_DIR = 
System.getProperty("IGNITE_CONFIG_DIR");
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_WORK_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/work";
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_LOG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/logs";
+
+    /** Default Ignite Config Directory. */
+    private static final String DEFAULT_CONFIG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/config";
+
+    /** Proxies for Multi-JVM nodes. */
+    private final ConcurrentMap<Integer, IgniteProcessProxy> gridProxies = new 
ConcurrentHashMap<>();
+
+    /** Configuration generator. */
+    private final IgniteConfigGenerator cfgGenerator = new 
IgniteConfigGenerator();
+
+    /** Local client instance. */
+    private IgniteEx locJvmInstance;
+
+    /** {@inheritDoc} */
+    @Override protected boolean isMultiJvm() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        deleteRootDir(WORK_DIR != null ? WORK_DIR : DEFAULT_WORK_DIR);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        for (int i : gridProxies.keySet())
+            stopNode(i);
+
+        gridProxies.clear();
+
+        locJvmInstance.close();
+
+        super.afterTest();
+    }
+
+    /** */
+    protected IgniteEx startBaseCluster(int nodeCnt) throws Exception {
+        IgniteEx ign = createOrRestartNode(1, true, false);
+
+        for (int i = 2; i <= nodeCnt; i++)
+            createOrRestartNode(i, true, false);
+
+        return ign;
+    }
+
+    /** */
+    protected IgniteEx startBaseNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected IgniteEx startBaseClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, true);
+    }
+
+    /** */
+    protected IgniteEx startTargetNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx startTargetClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, true);
+    }
+
+    /** */
+    protected IgniteEx upgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx downgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected void stopNode(int idx) throws Exception {
+        IgniteProcessProxy proxy = gridProxies.remove(idx);
+
+        if (proxy != null)
+            
IgniteProcessProxy.stop(proxy.configuration().getIgniteInstanceName(), true);
+    }
+
+    /**
+     * Centralized logic for node lifecycle.
+     */
+    private IgniteEx createOrRestartNode(int idx, boolean isBase, boolean 
isClient) throws Exception {
+        assert idx > 0 : "Remote node index must be greater than 0 (provided 
index: " + idx + ")";
+
+        if (gridProxies.containsKey(idx))
+            stopNode(idx);
+
+        File dist = isBase ? resolveBaseDist() : resolveTargetDist();
+        String workDir = resolveDir(WORK_DIR != null ? WORK_DIR : 
DEFAULT_WORK_DIR, idx);
+        String logDir = resolveDir(LOG_DIR != null ? LOG_DIR : 
DEFAULT_LOG_DIR, idx);
+        String cfgDir = resolveDir(CONFIG_DIR != null ? CONFIG_DIR : 
DEFAULT_CONFIG_DIR, idx);
+
+        IgniteConfiguration cfg;
+
+        if (gridProxies.containsKey(idx))
+            cfg = gridProxies.get(idx).configuration();
+        else {
+            cfg = getConfiguration(getTestIgniteInstanceName(idx));
+            cfg.setClientMode(isClient);
+            cfg.setWorkDirectory(workDir);
+        }
+
+        cfg.setIgniteHome(dist.getAbsolutePath());
+
+        Path xmlCfgPath = cfgGenerator.generateIgniteConfigurationXml(cfg, 
logDir, cfgDir);
+
+        String lbl = isBase ? "[BASE]" : "[TRGT]";
+
+        IgniteProcessProxy ign = new IgniteProcessProxy(
+            cfg,
+            log,
+            locJvmInstance == null ? null : () -> locJvmInstance,
+            false,
+            Collections.emptyList()) {
+            @Override protected IgniteLogger logger(IgniteLogger log, Object 
ctgr) {
+                return log.getLogger(ctgr + "#" + lbl + "node-" + idx);
+            }
+
+            @Override protected String igniteNodeRunnerClassName() throws 
Exception {
+                return CommandLineStartup.class.getCanonicalName();
+            }
+
+            @Override protected String params(IgniteConfiguration cfg, boolean 
resetDiscovery) throws Exception {
+                return xmlCfgPath.toAbsolutePath().toString();
+            }
+
+            @Override protected Collection<String> filteredJvmArgs() throws 
Exception {
+                Collection<String> filteredArgs = new ArrayList<>();
+
+                for (String arg : super.filteredJvmArgs()) {
+                    if (arg.startsWith("-cp") || arg.startsWith("-classpath"))
+                        continue; // Skip the parent's CP
+
+                    filteredArgs.add(arg);
+                }
+
+                String customCp = buildIgniteClasspath(dist);
+
+                filteredArgs.add("-DIGNITE_HOME=" + dist.getAbsolutePath());
+
+                filteredArgs.add("-cp");
+                filteredArgs.add(customCp);
+
+                return filteredArgs;
+            }
+        };
+
+        if (locJvmInstance == null) {
+            locJvmInstance = 
startClientGrid(getConfiguration(getTestIgniteInstanceName(0)));
+
+            ign.localJvmGrid(() -> locJvmInstance);
+        }
+
+        gridProxies.put(idx, ign);
+
+        return ign;
+    }
+
+    /**
+     * Resolves a specific directory for a node index and ensures it exists.
+     */
+    private String resolveDir(String root, int idx) {

Review Comment:
   static ?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/DistributionProvider.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.SystemProperty;
+import org.apache.ignite.internal.util.typedef.X;
+
+/** */
+public class DistributionProvider {
+    /** Base Ignite distribution location */
+    @SystemProperty(value = "Base Ignite distribution location", type = 
String.class)
+    private static final String BASE_DIST = "ignite.upgrade.base.dist";
+
+    /** Target Ignite distribution location */
+    @SystemProperty(value = "Target Ignite distribution location", type = 
String.class)
+    private static final String TARGET_DIST = "ignite.upgrade.target.dist";
+
+    /** Cached Ignite released distributions location */
+    @SystemProperty(value = "Cached Ignite released distributions location", 
type = String.class)
+    private static final String RELEASES_DIR = "ignite.upgrade.releases.dir";
+
+    /** */
+    private static final String DEFAULT_TARGET_REL_PATH = 
"target/release-package-apache-ignite";
+
+    /** */
+    private static final String LATEST_VERSION = "2.18.0";
+
+    /** Resolves the Target distribution. */
+    public static File resolveTargetDist() {
+        String explicit = IgniteSystemProperties.getString(TARGET_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cur = new File(System.getProperty("user.dir"));
+
+        while (cur != null) {
+            File potential = new File(cur, DEFAULT_TARGET_REL_PATH);
+
+            if (potential.exists() && potential.isDirectory())
+                return potential;
+
+            cur = cur.getParentFile();
+        }
+
+        throw new IllegalStateException(
+            "Target distribution not found. " +
+            "Build with '-Prelease' or set -D" + TARGET_DIST
+        );
+    }
+
+    /** Resolves the Base distribution. */
+    public static File resolveBaseDist() {
+        String explicit = IgniteSystemProperties.getString(BASE_DIST);
+
+        if (explicit != null && !explicit.isEmpty())
+            return new File(explicit);
+
+        File cacheDir = getCacheDir();
+        File versionedDir = new File(cacheDir, "ignite-" + LATEST_VERSION);
+
+        if (!versionedDir.exists())
+            downloadAndUnpack(LATEST_VERSION, versionedDir);
+
+        return versionedDir;
+    }
+
+    /** */
+    public static File getCacheDir() {
+        String tmpDir = Paths.get(System.getProperty("user.dir"), 
"/target/upgrade-test/ignite-releases").toString();
+        String path = IgniteSystemProperties.getString(RELEASES_DIR, tmpDir);
+
+        assert path != null : "Ignite releases directory is null!";
+
+        File dir = new File(path);
+
+        if (!dir.exists())
+            if (!dir.mkdirs())
+                throw new IllegalStateException("Failed to create directory: " 
+ dir.getAbsolutePath());
+
+        return dir;
+    }
+
+    /** Downloads and unpacks Ignite distribution from Apache Archives. */
+    private static void downloadAndUnpack(String ver, File dest) {
+        String zipName = "apache-ignite-" + ver + "-bin.zip";
+        String downloadUrl = "https://www.apache.org/dyn/closer.lua/ignite/"; + 
ver + "/" + zipName + "?action=download";
+        File tmpZip = new File(dest.getParentFile(), zipName);
+
+        try {
+            X.println("Downloading Ignite " + ver + " from " + downloadUrl + " 
to " + tmpZip.getAbsolutePath());
+
+            try {
+                downloadWithProgress(downloadUrl, tmpZip);
+            }
+            catch (IOException e) {
+                X.println("Download failed: " + e.getMessage());
+            }
+
+            X.println("Unpacking to " + 
dest.getParentFile().getAbsolutePath());
+
+            try (ZipInputStream zis = new ZipInputStream(new 
FileInputStream(tmpZip))) {
+                ZipEntry entry;
+                byte[] buf = new byte[8192];
+
+                while ((entry = zis.getNextEntry()) != null) {
+                    File file = new File(dest.getParentFile(), 
entry.getName());
+
+                    if (entry.isDirectory())
+                        file.mkdirs();
+                    else {
+                        file.getParentFile().mkdirs();
+
+                        try (OutputStream out = new FileOutputStream(file)) {
+                            int len;
+
+                            while ((len = zis.read(buf)) > 0)
+                                out.write(buf, 0, len);
+                        }
+                    }
+
+                    zis.closeEntry();
+                }
+            }
+
+            File extractedDir = new File(dest.getParentFile(), 
"apache-ignite-" + ver + "-bin");
+
+            if (extractedDir.exists()) {
+                if (!extractedDir.renameTo(dest))
+                    throw new IOException("Failed to rename " + extractedDir + 
" to " + dest);
+            }
+
+            tmpZip.delete();
+
+            X.println("Ignite " + ver + " is ready at " + 
dest.getAbsolutePath());
+        }
+        catch (IOException e) {
+            if (tmpZip.exists())
+                tmpZip.delete();
+
+            throw new RuntimeException("Failed to fetch or unpack Ignite " + 
ver, e);
+        }
+    }
+
+    /** */
+    private static void downloadWithProgress(String downloadUrl, File 
targetFile) throws IOException {
+        URL url = new URL(downloadUrl);
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+
+        long totalSize = connection.getContentLengthLong();
+
+        try (InputStream in = connection.getInputStream();
+             FileOutputStream out = new FileOutputStream(targetFile)) {
+
+            byte[] buf = new byte[8192];
+            int bytesRead;
+            long totalRead = 0;
+            int lastPercent = -1;
+
+            while ((bytesRead = in.read(buf)) != -1) {

Review Comment:
   Do we need a delay before printing on every byte or every % received?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteUpgradeAbstractTest.java:
##########
@@ -0,0 +1,292 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.startup.cmdline.CommandLineStartup;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveBaseDist;
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveTargetDist;
+
+/** */
+public abstract class IgniteUpgradeAbstractTest extends GridCommonAbstractTest 
{
+    /** Base Ignite Work Directory. */
+    public static final String WORK_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR);
+
+    /** Base Ignite Logging Directory. */
+    public static final String LOG_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_LOG_DIR);
+
+    /** Base Ignite Config Directory. */
+    public static final String CONFIG_DIR = 
System.getProperty("IGNITE_CONFIG_DIR");
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_WORK_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/work";
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_LOG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/logs";
+
+    /** Default Ignite Config Directory. */
+    private static final String DEFAULT_CONFIG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/config";
+
+    /** Proxies for Multi-JVM nodes. */
+    private final ConcurrentMap<Integer, IgniteProcessProxy> gridProxies = new 
ConcurrentHashMap<>();
+
+    /** Configuration generator. */
+    private final IgniteConfigGenerator cfgGenerator = new 
IgniteConfigGenerator();
+
+    /** Local client instance. */
+    private IgniteEx locJvmInstance;
+
+    /** {@inheritDoc} */
+    @Override protected boolean isMultiJvm() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        deleteRootDir(WORK_DIR != null ? WORK_DIR : DEFAULT_WORK_DIR);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        for (int i : gridProxies.keySet())
+            stopNode(i);
+
+        gridProxies.clear();
+
+        locJvmInstance.close();
+
+        super.afterTest();
+    }
+
+    /** */
+    protected IgniteEx startBaseCluster(int nodeCnt) throws Exception {
+        IgniteEx ign = createOrRestartNode(1, true, false);
+
+        for (int i = 2; i <= nodeCnt; i++)
+            createOrRestartNode(i, true, false);
+
+        return ign;
+    }
+
+    /** */
+    protected IgniteEx startBaseNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected IgniteEx startBaseClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, true);
+    }
+
+    /** */
+    protected IgniteEx startTargetNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx startTargetClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, true);
+    }
+
+    /** */
+    protected IgniteEx upgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx downgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected void stopNode(int idx) throws Exception {
+        IgniteProcessProxy proxy = gridProxies.remove(idx);
+
+        if (proxy != null)
+            
IgniteProcessProxy.stop(proxy.configuration().getIgniteInstanceName(), true);
+    }
+
+    /**
+     * Centralized logic for node lifecycle.

Review Comment:
   Looks like it doesn't stop a node. So, it is not a lifecycle. Let's 
simplify, smth. like 'starts or restarts node with required version'



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteUpgradeAbstractTest.java:
##########
@@ -0,0 +1,292 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.startup.cmdline.CommandLineStartup;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveBaseDist;
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveTargetDist;
+
+/** */
+public abstract class IgniteUpgradeAbstractTest extends GridCommonAbstractTest 
{
+    /** Base Ignite Work Directory. */
+    public static final String WORK_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR);
+
+    /** Base Ignite Logging Directory. */
+    public static final String LOG_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_LOG_DIR);
+
+    /** Base Ignite Config Directory. */
+    public static final String CONFIG_DIR = 
System.getProperty("IGNITE_CONFIG_DIR");
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_WORK_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/work";
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_LOG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/logs";
+
+    /** Default Ignite Config Directory. */
+    private static final String DEFAULT_CONFIG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/config";
+
+    /** Proxies for Multi-JVM nodes. */
+    private final ConcurrentMap<Integer, IgniteProcessProxy> gridProxies = new 
ConcurrentHashMap<>();
+
+    /** Configuration generator. */
+    private final IgniteConfigGenerator cfgGenerator = new 
IgniteConfigGenerator();
+
+    /** Local client instance. */
+    private IgniteEx locJvmInstance;
+
+    /** {@inheritDoc} */
+    @Override protected boolean isMultiJvm() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        deleteRootDir(WORK_DIR != null ? WORK_DIR : DEFAULT_WORK_DIR);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        for (int i : gridProxies.keySet())
+            stopNode(i);
+
+        gridProxies.clear();
+
+        locJvmInstance.close();
+
+        super.afterTest();
+    }
+
+    /** */
+    protected IgniteEx startBaseCluster(int nodeCnt) throws Exception {
+        IgniteEx ign = createOrRestartNode(1, true, false);
+
+        for (int i = 2; i <= nodeCnt; i++)
+            createOrRestartNode(i, true, false);
+
+        return ign;
+    }
+
+    /** */
+    protected IgniteEx startBaseNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected IgniteEx startBaseClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, true);
+    }
+
+    /** */
+    protected IgniteEx startTargetNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx startTargetClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, true);
+    }
+
+    /** */
+    protected IgniteEx upgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx downgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected void stopNode(int idx) throws Exception {
+        IgniteProcessProxy proxy = gridProxies.remove(idx);
+
+        if (proxy != null)
+            
IgniteProcessProxy.stop(proxy.configuration().getIgniteInstanceName(), true);
+    }
+
+    /**
+     * Centralized logic for node lifecycle.
+     */
+    private IgniteEx createOrRestartNode(int idx, boolean isBase, boolean 
isClient) throws Exception {
+        assert idx > 0 : "Remote node index must be greater than 0 (provided 
index: " + idx + ")";
+
+        if (gridProxies.containsKey(idx))
+            stopNode(idx);
+
+        File dist = isBase ? resolveBaseDist() : resolveTargetDist();
+        String workDir = resolveDir(WORK_DIR != null ? WORK_DIR : 
DEFAULT_WORK_DIR, idx);
+        String logDir = resolveDir(LOG_DIR != null ? LOG_DIR : 
DEFAULT_LOG_DIR, idx);
+        String cfgDir = resolveDir(CONFIG_DIR != null ? CONFIG_DIR : 
DEFAULT_CONFIG_DIR, idx);
+
+        IgniteConfiguration cfg;
+
+        if (gridProxies.containsKey(idx))
+            cfg = gridProxies.get(idx).configuration();
+        else {
+            cfg = getConfiguration(getTestIgniteInstanceName(idx));
+            cfg.setClientMode(isClient);
+            cfg.setWorkDirectory(workDir);
+        }
+
+        cfg.setIgniteHome(dist.getAbsolutePath());
+
+        Path xmlCfgPath = cfgGenerator.generateIgniteConfigurationXml(cfg, 
logDir, cfgDir);
+
+        String lbl = isBase ? "[BASE]" : "[TRGT]";
+
+        IgniteProcessProxy ign = new IgniteProcessProxy(
+            cfg,
+            log,
+            locJvmInstance == null ? null : () -> locJvmInstance,
+            false,
+            Collections.emptyList()) {
+            @Override protected IgniteLogger logger(IgniteLogger log, Object 
ctgr) {
+                return log.getLogger(ctgr + "#" + lbl + "node-" + idx);
+            }
+
+            @Override protected String igniteNodeRunnerClassName() throws 
Exception {
+                return CommandLineStartup.class.getCanonicalName();
+            }
+
+            @Override protected String params(IgniteConfiguration cfg, boolean 
resetDiscovery) throws Exception {
+                return xmlCfgPath.toAbsolutePath().toString();
+            }
+
+            @Override protected Collection<String> filteredJvmArgs() throws 
Exception {
+                Collection<String> filteredArgs = new ArrayList<>();
+
+                for (String arg : super.filteredJvmArgs()) {
+                    if (arg.startsWith("-cp") || arg.startsWith("-classpath"))
+                        continue; // Skip the parent's CP
+
+                    filteredArgs.add(arg);
+                }
+
+                String customCp = buildIgniteClasspath(dist);
+
+                filteredArgs.add("-DIGNITE_HOME=" + dist.getAbsolutePath());
+
+                filteredArgs.add("-cp");
+                filteredArgs.add(customCp);
+
+                return filteredArgs;
+            }
+        };
+
+        if (locJvmInstance == null) {
+            locJvmInstance = 
startClientGrid(getConfiguration(getTestIgniteInstanceName(0)));
+
+            ign.localJvmGrid(() -> locJvmInstance);
+        }
+
+        gridProxies.put(idx, ign);
+
+        return ign;
+    }
+
+    /**
+     * Resolves a specific directory for a node index and ensures it exists.
+     */
+    private String resolveDir(String root, int idx) {
+        File dir = new File(root, "node_" + idx);
+
+        if (!dir.exists())
+            dir.mkdirs();
+
+        return dir.getAbsolutePath();
+    }
+
+    /**
+     * Deletes the entire root directory and all subdirectories/files.
+     */
+    private void deleteRootDir(String root) throws IOException {
+        Path rootPath = Paths.get(root);
+
+        if (Files.exists(rootPath)) {
+            try (Stream<Path> walk = Files.walk(rootPath)) {
+                walk.sorted(Comparator.reverseOrder())
+                    .map(Path::toFile)
+                    .forEach(File::delete);
+            }
+        }
+    }
+
+    /**
+     * Builds a classpath string based on the provided Ignite home directory.
+     *
+     * @param igniteHome The root directory of the Ignite distribution.

Review Comment:
   Ignite home path?



##########
modules/compatibility/src/test/java/org/apache/ignite/compatibility/upgrade/IgniteUpgradeAbstractTest.java:
##########
@@ -0,0 +1,292 @@
+/*
+ * 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.compatibility.upgrade;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.startup.cmdline.CommandLineStartup;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveBaseDist;
+import static 
org.apache.ignite.compatibility.upgrade.DistributionProvider.resolveTargetDist;
+
+/** */
+public abstract class IgniteUpgradeAbstractTest extends GridCommonAbstractTest 
{
+    /** Base Ignite Work Directory. */
+    public static final String WORK_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR);
+
+    /** Base Ignite Logging Directory. */
+    public static final String LOG_DIR = 
System.getenv(IgniteSystemProperties.IGNITE_LOG_DIR);
+
+    /** Base Ignite Config Directory. */
+    public static final String CONFIG_DIR = 
System.getProperty("IGNITE_CONFIG_DIR");
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_WORK_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/work";
+
+    /** Default Ignite Work Directory. */
+    private static final String DEFAULT_LOG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/logs";
+
+    /** Default Ignite Config Directory. */
+    private static final String DEFAULT_CONFIG_DIR = 
System.getProperty("user.dir") + "/target/upgrade-test/config";
+
+    /** Proxies for Multi-JVM nodes. */
+    private final ConcurrentMap<Integer, IgniteProcessProxy> gridProxies = new 
ConcurrentHashMap<>();
+
+    /** Configuration generator. */
+    private final IgniteConfigGenerator cfgGenerator = new 
IgniteConfigGenerator();
+
+    /** Local client instance. */
+    private IgniteEx locJvmInstance;
+
+    /** {@inheritDoc} */
+    @Override protected boolean isMultiJvm() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        deleteRootDir(WORK_DIR != null ? WORK_DIR : DEFAULT_WORK_DIR);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        for (int i : gridProxies.keySet())
+            stopNode(i);
+
+        gridProxies.clear();
+
+        locJvmInstance.close();
+
+        super.afterTest();
+    }
+
+    /** */
+    protected IgniteEx startBaseCluster(int nodeCnt) throws Exception {
+        IgniteEx ign = createOrRestartNode(1, true, false);
+
+        for (int i = 2; i <= nodeCnt; i++)
+            createOrRestartNode(i, true, false);
+
+        return ign;
+    }
+
+    /** */
+    protected IgniteEx startBaseNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected IgniteEx startBaseClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, true);
+    }
+
+    /** */
+    protected IgniteEx startTargetNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx startTargetClientNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, true);
+    }
+
+    /** */
+    protected IgniteEx upgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, false, false);
+    }
+
+    /** */
+    protected IgniteEx downgradeNode(int idx) throws Exception {
+        return createOrRestartNode(idx, true, false);
+    }
+
+    /** */
+    protected void stopNode(int idx) throws Exception {
+        IgniteProcessProxy proxy = gridProxies.remove(idx);
+
+        if (proxy != null)
+            
IgniteProcessProxy.stop(proxy.configuration().getIgniteInstanceName(), true);
+    }
+
+    /**
+     * Centralized logic for node lifecycle.
+     */
+    private IgniteEx createOrRestartNode(int idx, boolean isBase, boolean 
isClient) throws Exception {
+        assert idx > 0 : "Remote node index must be greater than 0 (provided 
index: " + idx + ")";
+
+        if (gridProxies.containsKey(idx))
+            stopNode(idx);
+
+        File dist = isBase ? resolveBaseDist() : resolveTargetDist();
+        String workDir = resolveDir(WORK_DIR != null ? WORK_DIR : 
DEFAULT_WORK_DIR, idx);
+        String logDir = resolveDir(LOG_DIR != null ? LOG_DIR : 
DEFAULT_LOG_DIR, idx);
+        String cfgDir = resolveDir(CONFIG_DIR != null ? CONFIG_DIR : 
DEFAULT_CONFIG_DIR, idx);
+
+        IgniteConfiguration cfg;
+
+        if (gridProxies.containsKey(idx))
+            cfg = gridProxies.get(idx).configuration();
+        else {
+            cfg = getConfiguration(getTestIgniteInstanceName(idx));
+            cfg.setClientMode(isClient);
+            cfg.setWorkDirectory(workDir);
+        }
+
+        cfg.setIgniteHome(dist.getAbsolutePath());
+
+        Path xmlCfgPath = cfgGenerator.generateIgniteConfigurationXml(cfg, 
logDir, cfgDir);
+
+        String lbl = isBase ? "[BASE]" : "[TRGT]";
+
+        IgniteProcessProxy ign = new IgniteProcessProxy(
+            cfg,
+            log,
+            locJvmInstance == null ? null : () -> locJvmInstance,
+            false,
+            Collections.emptyList()) {
+            @Override protected IgniteLogger logger(IgniteLogger log, Object 
ctgr) {
+                return log.getLogger(ctgr + "#" + lbl + "node-" + idx);
+            }
+
+            @Override protected String igniteNodeRunnerClassName() throws 
Exception {
+                return CommandLineStartup.class.getCanonicalName();
+            }
+
+            @Override protected String params(IgniteConfiguration cfg, boolean 
resetDiscovery) throws Exception {
+                return xmlCfgPath.toAbsolutePath().toString();
+            }
+
+            @Override protected Collection<String> filteredJvmArgs() throws 
Exception {
+                Collection<String> filteredArgs = new ArrayList<>();
+
+                for (String arg : super.filteredJvmArgs()) {
+                    if (arg.startsWith("-cp") || arg.startsWith("-classpath"))
+                        continue; // Skip the parent's CP
+
+                    filteredArgs.add(arg);
+                }
+
+                String customCp = buildIgniteClasspath(dist);
+
+                filteredArgs.add("-DIGNITE_HOME=" + dist.getAbsolutePath());
+
+                filteredArgs.add("-cp");
+                filteredArgs.add(customCp);
+
+                return filteredArgs;
+            }
+        };
+
+        if (locJvmInstance == null) {
+            locJvmInstance = 
startClientGrid(getConfiguration(getTestIgniteInstanceName(0)));
+
+            ign.localJvmGrid(() -> locJvmInstance);
+        }
+
+        gridProxies.put(idx, ign);
+
+        return ign;
+    }
+
+    /**
+     * Resolves a specific directory for a node index and ensures it exists.
+     */
+    private String resolveDir(String root, int idx) {
+        File dir = new File(root, "node_" + idx);
+
+        if (!dir.exists())
+            dir.mkdirs();
+
+        return dir.getAbsolutePath();
+    }
+
+    /**
+     * Deletes the entire root directory and all subdirectories/files.
+     */
+    private void deleteRootDir(String root) throws IOException {

Review Comment:
   Static? Can we use _U#delete()_?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to