You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by tk...@apache.org on 2022/10/06 14:16:40 UTC

[ignite] branch master updated: IGNITE-17805 Fail a test if it tries to launch remote JVM with different major Java version (#10287)

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

tkalkirill pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 830e7315265 IGNITE-17805 Fail a test if it tries to launch remote JVM with different major Java version (#10287)
830e7315265 is described below

commit 830e73152652b6ddbf48b83a35acd7522445f0e4
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Thu Oct 6 18:16:30 2022 +0400

    IGNITE-17805 Fail a test if it tries to launch remote JVM with different major Java version (#10287)
---
 .../junits/IgniteCompatibilityAbstractTest.java    |  2 +-
 .../ignite/internal/util/GridJavaProcess.java      | 22 ++++++-
 .../testframework/junits/GridAbstractTest.java     |  2 +-
 .../junits/multijvm/IgniteProcessProxy.java        | 49 ++++++++++----
 .../junits/multijvm/JavaVersionCommand.java        | 68 ++++++++++++++++++++
 .../junits/multijvm/JavaVersionCommandParser.java  | 48 ++++++++++++++
 .../multijvm/JavaVersionCommandParserTest.java     | 74 ++++++++++++++++++++++
 .../ignite/testsuites/IgniteBasicTestSuite.java    |  2 +
 8 files changed, 249 insertions(+), 18 deletions(-)

diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java
index 46e94db7c2e..384d17bde6f 100644
--- a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java
+++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java
@@ -145,7 +145,7 @@ public abstract class IgniteCompatibilityAbstractTest extends GridCommonAbstract
 
         final ListeningTestLogger logger = new ListeningTestLogger(log);
 
-        IgniteProcessProxy ignite = new IgniteProcessProxy(cfg, logger, locJvmInstance == null ? null : (x) -> locJvmInstance, true) {
+        IgniteProcessProxy ignite = new IgniteProcessProxy(cfg, logger, locJvmInstance == null ? null : () -> locJvmInstance, true) {
             @Override protected IgniteLogger logger(IgniteLogger log, Object ctgr) {
                 return logger.getLogger(ctgr + "#" + ver.replaceAll("\\.", "_"));
             }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/GridJavaProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/util/GridJavaProcess.java
index 30312e6f835..be2a6c275c9 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/GridJavaProcess.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/GridJavaProcess.java
@@ -142,8 +142,7 @@ public final class GridJavaProcess {
 
         List<String> procCommands = new ArrayList<>();
 
-        String javaBin = (javaHome == null ? System.getProperty("java.home") : javaHome) +
-            File.separator + "bin" + File.separator + "java";
+        String javaBin = resolveJavaBin(javaHome);
 
         procCommands.add(javaBin);
         procCommands.addAll(jvmArgs == null ? U.jvmArgs() : jvmArgs);
@@ -183,6 +182,25 @@ public final class GridJavaProcess {
         return gjProc;
     }
 
+    /**
+     * Resolves path to java binary (that can be executed using exec). Either the provided java home directory
+     * is used, or, if it's {@code null}, the java.home system property is consulted with.
+     *
+     * @param javaHome Java home directory where to look for bin/java; if {@code null}, then java.home property value is used.
+     * @return Path to Java executable.
+     */
+    public static String resolveJavaBin(@Nullable String javaHome) {
+        return resolveJavaHome(javaHome) + File.separator + "bin" + File.separator + "java";
+    }
+
+    /**
+     * Returns the provided java home path or, if it's {@code null}, falls back to the path obtained via 'java.home'
+     * system property.
+     */
+    private static String resolveJavaHome(@Nullable String javaHome) {
+        return javaHome == null ? System.getProperty("java.home") : javaHome;
+    }
+
     /**
      * Kills the java process.
      *
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridAbstractTest.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridAbstractTest.java
index d61a62e982e..95b0ae37abe 100755
--- a/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridAbstractTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/GridAbstractTest.java
@@ -1429,7 +1429,7 @@ public abstract class GridAbstractTest extends JUnitAssertAware {
             }
         }
 
-        return new IgniteProcessProxy(cfg, cfg.getGridLogger(), (x) -> grid(0), resetDiscovery, additionalRemoteJvmArgs());
+        return new IgniteProcessProxy(cfg, cfg.getGridLogger(), () -> grid(0), resetDiscovery, additionalRemoteJvmArgs());
     }
 
     /**
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/IgniteProcessProxy.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/IgniteProcessProxy.java
index 436fd8abd53..7080a59e817 100644
--- a/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/IgniteProcessProxy.java
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/IgniteProcessProxy.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.testframework.junits.multijvm;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -28,7 +29,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
+import java.util.function.Supplier;
 import javax.cache.CacheException;
 import org.apache.ignite.DataRegionMetrics;
 import org.apache.ignite.DataRegionMetricsAdapter;
@@ -101,6 +102,8 @@ import org.apache.ignite.testframework.junits.IgniteTestResources;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import static org.junit.Assert.fail;
+
 /**
  * Ignite proxy for ignite instance at another JVM.
  */
@@ -122,7 +125,7 @@ public class IgniteProcessProxy implements IgniteEx {
     private final transient IgniteConfiguration cfg;
 
     /** Local JVM grid. */
-    private final transient Function<Void, Ignite> locJvmGrid;
+    private final transient Supplier<Ignite> locJvmGrid;
 
     /** Logger. */
     private final transient IgniteLogger log;
@@ -138,7 +141,7 @@ public class IgniteProcessProxy implements IgniteEx {
      */
     public IgniteProcessProxy(IgniteConfiguration cfg, IgniteLogger log, Ignite locJvmGrid)
         throws Exception {
-        this(cfg, log, (Function<Void, Ignite>)locJvmGrid, true);
+        this(cfg, log, locJvmGrid == null ? null : () -> locJvmGrid, true);
     }
 
     /**
@@ -147,7 +150,7 @@ public class IgniteProcessProxy implements IgniteEx {
      * @param locJvmGrid Local JVM grid.
      * @throws Exception On error.
      */
-    public IgniteProcessProxy(IgniteConfiguration cfg, IgniteLogger log, Function<Void, Ignite> locJvmGrid, boolean discovery)
+    public IgniteProcessProxy(IgniteConfiguration cfg, IgniteLogger log, Supplier<Ignite> locJvmGrid, boolean discovery)
         throws Exception {
         this(cfg, log, locJvmGrid, discovery, Collections.emptyList());
     }
@@ -162,7 +165,7 @@ public class IgniteProcessProxy implements IgniteEx {
     public IgniteProcessProxy(
         IgniteConfiguration cfg,
         IgniteLogger log,
-        Function<Void, Ignite> locJvmGrid,
+        Supplier<Ignite> locJvmGrid,
         boolean resetDiscovery,
         List<String> additionalArgs
     )
@@ -172,6 +175,9 @@ public class IgniteProcessProxy implements IgniteEx {
         this.locJvmGrid = locJvmGrid;
         this.log = logger(log, "jvm-" + id.toString().substring(0, id.toString().indexOf('-')));
 
+        final String javaHome = System.getProperty(TEST_MULTIJVM_JAVA_HOME);
+        validateRemoteJre(javaHome);
+
         String params = params(cfg, resetDiscovery);
 
         Collection<String> filteredJvmArgs = filteredJvmArgs();
@@ -180,7 +186,7 @@ public class IgniteProcessProxy implements IgniteEx {
         final CountDownLatch rmtNodeStartedLatch = new CountDownLatch(1);
 
         if (locJvmGrid != null)
-            locJvmGrid.apply(null).events()
+            locJvmGrid.get().events()
                 .localListen(new NodeStartedListener(id, rmtNodeStartedLatch), EventType.EVT_NODE_JOINED);
 
         proc = GridJavaProcess.exec(
@@ -188,14 +194,10 @@ public class IgniteProcessProxy implements IgniteEx {
             params,
             this.log,
             // Optional closure to be called each time wrapped process prints line to system.out or system.err.
-            new IgniteInClosure<String>() {
-                @Override public void apply(String s) {
-                    IgniteProcessProxy.this.log.info(s);
-                }
-            },
+            (IgniteInClosure<String>)this.log::info,
             null,
-            System.getProperty(TEST_MULTIJVM_JAVA_HOME),
-            filteredJvmArgs, // JVM Args.
+            javaHome,
+            filteredJvmArgs,
             System.getProperty("surefire.test.class.path")
         );
 
@@ -212,6 +214,25 @@ public class IgniteProcessProxy implements IgniteEx {
         }
     }
 
+    /**
+     * Validates that the JRE corresponding to the given Java home is valid for use as a remote JVM.
+     * This currently means only checking that its major version matches the major version of the JRE we run on.
+     *
+     * @param javaHome Java home.
+     * @throws IOException          If I/O fails when interacting with 'java' process.
+     * @throws InterruptedException If we get interrupted.
+     */
+    private static void validateRemoteJre(@Nullable String javaHome) throws IOException, InterruptedException {
+        int remoteMajorVersion = new JavaVersionCommand().majorVersion(javaHome);
+        int localMajorVersion = U.majorJavaVersion(System.getProperty("java.version"));
+
+        if (localMajorVersion != remoteMajorVersion) {
+            fail("Version of remote java with home at '" + javaHome + "' (" + remoteMajorVersion +
+                ") is different from local java version (" + localMajorVersion + "). " +
+                "Make sure test.multijvm.java.home property specifies a path to a correct Java installation");
+        }
+    }
+
     /**
      * Creates new logger instance based on given logger and given category.
      *
@@ -432,7 +453,7 @@ public class IgniteProcessProxy implements IgniteEx {
      * @return Local JVM grid instance.
      */
     public Ignite localJvmGrid() {
-        return locJvmGrid.apply(null);
+        return locJvmGrid.get();
     }
 
     /**
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommand.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommand.java
new file mode 100644
index 00000000000..c0ebf16df05
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommand.java
@@ -0,0 +1,68 @@
+/*
+ * 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.testframework.junits.multijvm;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.io.IOUtils;
+import org.apache.ignite.internal.util.GridJavaProcess;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Utility to work with 'java -version' command.
+ */
+class JavaVersionCommand {
+    /**
+     * Obtains major Java version for Java located at the given path.
+     *
+     * @param javaHome Java home path.
+     * @return Major Java version (like 8 or 11).
+     * @throws IOException If something goes wrong.
+     */
+    int majorVersion(String javaHome) throws IOException, InterruptedException {
+        Process process = new ProcessBuilder(GridJavaProcess.resolveJavaBin(javaHome), "-version").start();
+        assertTrue(process.waitFor(10, TimeUnit.SECONDS));
+
+        if (process.exitValue() != 0) {
+            throw new IllegalStateException("'java -version' failed, stdin '" +
+                readStream(process.getInputStream()) + "', stdout '" +
+                readStream(process.getErrorStream()) + "'");
+        }
+
+        String versionOutput = readStream(process.getErrorStream());
+
+        return JavaVersionCommandParser.extractMajorVersion(versionOutput);
+    }
+
+    /**
+     * Reads whole stream content and returns it as a string. UTF-8 is used to convert from bytes to string.
+     *
+     * @param inputStream   Stream to read.
+     * @return Stream content as a string.
+     * @throws IOException If something goes wrong.
+     */
+    private String readStream(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        IOUtils.copy(inputStream, baos);
+        return new String(baos.toByteArray(), UTF_8);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommandParser.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommandParser.java
new file mode 100644
index 00000000000..2ce890ca8d5
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommandParser.java
@@ -0,0 +1,48 @@
+/*
+ * 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.testframework.junits.multijvm;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Parses output of 'java -version'.
+ */
+class JavaVersionCommandParser {
+    /** Pattern for parsing 'java -version' command output. */
+    private static final Pattern versionPattern = Pattern.compile("java version \"([^\"]+)\".*", Pattern.DOTALL);
+
+    /**
+     * Extracts major java version (like '17' or '1.8') from 'java -version' output.
+     *
+     * @param versionCommandOutput Output to parse.
+     * @return Major java version.
+     */
+    static int extractMajorVersion(String versionCommandOutput) {
+        Matcher matcher = versionPattern.matcher(versionCommandOutput);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Cannot parse the following as java version output: '" +
+                versionCommandOutput + "'");
+        }
+
+        String fullJavaVersion = matcher.group(1);
+
+        return U.majorJavaVersion(fullJavaVersion);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommandParserTest.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommandParserTest.java
new file mode 100644
index 00000000000..cbd12b910b4
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/multijvm/JavaVersionCommandParserTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.testframework.junits.multijvm;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for {@link JavaVersionCommandParser}.
+ */
+@RunWith(Parameterized.class)
+public class JavaVersionCommandParserTest {
+    /** 'java -version' command output. */
+    @Parameterized.Parameter(0)
+    public String commandOutput;
+
+    /** Major version corresponding to the output. */
+    @Parameterized.Parameter(1)
+    public int majorVersion;
+
+    /** Returns dataset for testing major version extraction. */
+    @Parameterized.Parameters()
+    public static Collection<Object[]> dataset() {
+        return Arrays.asList(
+            new Object[]{
+                "java version \"1.8.0_311\"\n" +
+                    "Java(TM) SE Runtime Environment (build 1.8.0_311-b11)\n" +
+                    "Java HotSpot(TM) Server VM (build 25.311-b11, mixed mode)",
+                8
+            },
+            new Object[]{
+                "java version \"11.0.6\" 2020-01-14 LTS\n" +
+                    "Java(TM) SE Runtime Environment 18.9 (build 11.0.6+8-LTS)\n" +
+                    "Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.6+8-LTS, mixed mode)",
+                11
+            },
+            new Object[]{
+                "java version \"19\" 2022-09-20\n" +
+                    "Java(TM) SE Runtime Environment (build 19+36-2238)\n" +
+                    "Java HotSpot(TM) 64-Bit Server VM (build 19+36-2238, mixed mode, sharing)\n",
+                19
+            }
+        );
+    }
+
+    /**
+     * Tests major version extraction.
+     */
+    @Test
+    public void extractsMajorVersion() {
+        assertThat(JavaVersionCommandParser.extractMajorVersion(commandOutput), is(majorVersion));
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index 6e2dd6c693b..3ae20c00739 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -67,6 +67,7 @@ import org.apache.ignite.messaging.IgniteMessagingSendAsyncTest;
 import org.apache.ignite.messaging.IgniteMessagingWithClientTest;
 import org.apache.ignite.spi.GridSpiLocalHostInjectionTest;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTestSelfTest;
+import org.apache.ignite.testframework.junits.multijvm.JavaVersionCommandParserTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
@@ -143,6 +144,7 @@ import org.junit.runners.Suite;
     OdbcConfigurationValidationSelfTest.class,
     OdbcEscapeSequenceSelfTest.class,
     SqlListenerUtilsTest.class,
+    JavaVersionCommandParserTest.class
 })
 public class IgniteBasicTestSuite {
 }