This is an automated email from the ASF dual-hosted git repository. ptupitsyn pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new 31292e3213d IGNITE-26312 .NET: Fix port issues in compat tests (#6545) 31292e3213d is described below commit 31292e3213d0621bc114ee1318aa82fbc0033090 Author: Pavel Tupitsyn <ptupit...@apache.org> AuthorDate: Wed Sep 3 17:02:13 2025 +0300 IGNITE-26312 .NET: Fix port issues in compat tests (#6545) --- .../PlatformCompatibilityTestNodeRunner.java | 33 ++++--- .../CurrentClientWithOldServerCompatibilityTest.cs | 7 +- .../dotnet/Apache.Ignite.Tests/JavaServer.cs | 106 +++++++++++++-------- 3 files changed, 89 insertions(+), 57 deletions(-) diff --git a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/PlatformCompatibilityTestNodeRunner.java b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/PlatformCompatibilityTestNodeRunner.java index 87d08986c14..9b19fe29992 100644 --- a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/PlatformCompatibilityTestNodeRunner.java +++ b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/PlatformCompatibilityTestNodeRunner.java @@ -17,10 +17,6 @@ package org.apache.ignite.internal; -import static org.apache.ignite.internal.ClusterConfiguration.DEFAULT_BASE_CLIENT_PORT; -import static org.apache.ignite.internal.ClusterConfiguration.DEFAULT_BASE_HTTPS_PORT; -import static org.apache.ignite.internal.ClusterConfiguration.DEFAULT_BASE_HTTP_PORT; -import static org.apache.ignite.internal.ClusterConfiguration.DEFAULT_BASE_PORT; import static org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIMEM_PROFILE_NAME; import static org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIPERSIST_PROFILE_NAME; import static org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_ROCKSDB_PROFILE_NAME; @@ -59,22 +55,21 @@ public class PlatformCompatibilityTestNodeRunner { * @param args Args. */ public static void main(String[] args) throws Exception { - String version = System.getenv("IGNITE_OLD_SERVER_VERSION"); - String workDir = System.getenv("IGNITE_OLD_SERVER_WORK_DIR"); - int portOffset = Integer.parseInt(System.getenv().getOrDefault("IGNITE_OLD_SERVER_PORT_OFFSET", "20000")); + String version = getEnvOrThrow("IGNITE_OLD_SERVER_VERSION"); + String workDir = getEnvOrThrow("IGNITE_OLD_SERVER_WORK_DIR"); - if (version == null || workDir == null) { - throw new Exception("IGNITE_OLD_SERVER_VERSION and IGNITE_OLD_SERVER_WORK_DIR environment variables are not set."); - } + int port = Integer.parseInt(getEnvOrThrow("IGNITE_OLD_SERVER_PORT")); + int httpPort = Integer.parseInt(getEnvOrThrow("IGNITE_OLD_SERVER_HTTP_PORT")); + int clientPort = Integer.parseInt(getEnvOrThrow("IGNITE_OLD_SERVER_CLIENT_PORT")); System.out.println(">>> Starting test node with version: " + version + " in work directory: " + workDir); + System.out.println(">>> Ports: node=" + port + ", http=" + httpPort + ", client=" + clientPort); ClusterConfiguration clusterConfiguration = ClusterConfiguration.builder(new PlatformTestInfo(), Path.of(workDir)) .defaultNodeBootstrapConfigTemplate(NODE_BOOTSTRAP_CFG_TEMPLATE) - .basePort(DEFAULT_BASE_PORT + portOffset) - .baseHttpPort(DEFAULT_BASE_HTTP_PORT + portOffset) - .baseHttpsPort(DEFAULT_BASE_HTTPS_PORT + portOffset) - .baseClientPort(DEFAULT_BASE_CLIENT_PORT + portOffset) + .basePort(port) + .baseHttpPort(httpPort) + .baseClientPort(clientPort) .build(); var cluster = new IgniteCluster(clusterConfiguration); @@ -92,6 +87,16 @@ public class PlatformCompatibilityTestNodeRunner { cluster.stop(); } + private static String getEnvOrThrow(String name) throws Exception { + String val = System.getenv(name); + + if (val == null || val.isEmpty()) { + throw new Exception(name + " environment variable is not set or empty."); + } + + return val; + } + private static class PlatformTestInfo implements TestInfo { @Override public String getDisplayName() { diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Compatibility/CurrentClientWithOldServerCompatibilityTest.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Compatibility/CurrentClientWithOldServerCompatibilityTest.cs index 1c3646e2975..a9fc4e62e34 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compatibility/CurrentClientWithOldServerCompatibilityTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compatibility/CurrentClientWithOldServerCompatibilityTest.cs @@ -75,8 +75,11 @@ public class CurrentClientWithOldServerCompatibilityTest [OneTimeTearDown] public void OneTimeTearDown() { - _client.Dispose(); - _javaServer.Dispose(); + // ReSharper disable ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + _client?.Dispose(); + _javaServer?.Dispose(); + + // ReSharper restore ConditionalAccessQualifierIsNonNullableAccordingToAPIContract _workDir.Dispose(); } diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/JavaServer.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/JavaServer.cs index 74392f3dc5a..12d02b80233 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Tests/JavaServer.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Tests/JavaServer.cs @@ -23,6 +23,8 @@ namespace Apache.Ignite.Tests using System.Globalization; using System.IO; using System.Linq; + using System.Net; + using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -36,10 +38,6 @@ namespace Apache.Ignite.Tests private const string GradleOptsEnvVar = "IGNITE_DOTNET_GRADLE_OPTS"; private const string RequireExternalJavaServerEnvVar = "IGNITE_DOTNET_REQUIRE_EXTERNAL_SERVER"; - private const int DefaultClientPort = 10942; - - private const int DefaultClientPortOldServer = 10800; - private const int ConnectTimeoutSeconds = 4 * 60; private const string GradleCommandExec = ":ignite-runner:runnerPlatformTest --parallel"; @@ -66,13 +64,12 @@ namespace Apache.Ignite.Tests /// Starts a server node. /// </summary> /// <returns>Disposable object to stop the server.</returns> - public static async Task<JavaServer> StartAsync() => await StartInternalAsync(old: false, env: []); + public static async Task<JavaServer> StartAsync() => await StartInternalAsync(old: false, env: [], defaultPort: 10942); public static async Task<JavaServer> StartOldAsync(string version, string workDir) { - // Calculate port offset based on the server version (minor + patch) to avoid conflicts with other tests. - // This way we can have multiple active nodes with different versions (separate clusters). - var portOffset = 20_000 + int.Parse(version[2..].Replace(".", string.Empty)); + // Get random unused ports to avoid conflicts with other tests. + var ports = GetUnusedPorts(3); return await StartInternalAsync( old: true, @@ -80,9 +77,10 @@ namespace Apache.Ignite.Tests { { "IGNITE_OLD_SERVER_VERSION", version }, { "IGNITE_OLD_SERVER_WORK_DIR", workDir }, - { "IGNITE_OLD_SERVER_PORT_OFFSET", portOffset.ToString(CultureInfo.InvariantCulture) } - }, - portOffset: portOffset); + { "IGNITE_OLD_SERVER_PORT", ports[0].ToString(CultureInfo.InvariantCulture) }, + { "IGNITE_OLD_SERVER_HTTP_PORT", ports[1].ToString(CultureInfo.InvariantCulture) }, + { "IGNITE_OLD_SERVER_CLIENT_PORT", ports[2].ToString(CultureInfo.InvariantCulture) }, + }); } public void Dispose() @@ -109,17 +107,16 @@ namespace Apache.Ignite.Tests Log(">>> Java server stopped."); } - private static async Task<JavaServer> StartInternalAsync(bool old, Dictionary<string, string?> env, int portOffset = 0) + private static async Task<JavaServer> StartInternalAsync(bool old, Dictionary<string, string?> env, int? defaultPort = null) { string gradleCommand = old ? GradleCommandExecOldServer : GradleCommandExec; - int defaultPort = (old ? DefaultClientPortOldServer : DefaultClientPort) + portOffset; - if (await TryConnect(defaultPort) == null) + if (defaultPort != null && await TryConnect(defaultPort.Value) == null) { // Server started from outside. Log(">>> Java server is already started on port " + defaultPort + "."); - return new JavaServer([defaultPort], null); + return new JavaServer([defaultPort.Value], null); } if (bool.TryParse(Environment.GetEnvironmentVariable(RequireExternalJavaServerEnvVar), out var requireExternalServer) @@ -133,8 +130,7 @@ namespace Apache.Ignite.Tests var process = CreateProcess(gradleCommand, env); - var evt = new ManualResetEventSlim(false); - int[]? ports = null; + var tcs = new TaskCompletionSource<int[]>(); DataReceivedEventHandler handler = (_, eventArgs) => { @@ -148,8 +144,14 @@ namespace Apache.Ignite.Tests if (line.StartsWith("THIN_CLIENT_PORTS", StringComparison.Ordinal)) { - ports = line.Split('=').Last().Split(',').Select(int.Parse).OrderBy(x => x).ToArray(); - evt.Set(); + var ports = line.Split('=').Last().Split(',').Select(int.Parse).OrderBy(x => x).ToArray(); + tcs.SetResult(ports); + } + + if (line.StartsWith("Exception in thread \"main\"", StringComparison.OrdinalIgnoreCase)) + { + process.Kill(entireProcessTree: true); + tcs.SetException(new Exception($"Java server failed: {line}")); } }; @@ -158,37 +160,32 @@ namespace Apache.Ignite.Tests process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - if (!evt.Wait(TimeSpan.FromSeconds(ConnectTimeoutSeconds))) + try { - process.Kill(entireProcessTree: true); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); - throw new InvalidOperationException("Failed to wait for THIN_CLIENT_PORTS. Check logs for details."); - } + var ports = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(ConnectTimeoutSeconds)); + var port = ports.FirstOrDefault(); - if (ports == null) - { - process.Kill(entireProcessTree: true); + if (!WaitForServer(port)) + { + process.Kill(entireProcessTree: true); + KillProcessesOnPorts(ports); - throw new InvalidOperationException("Failed to get ports. Check logs for details."); - } + throw new InvalidOperationException( + $"Failed to wait for the server to start (can't connect the client on port {port}). Check logs for details."); + } - var port = ports.FirstOrDefault(); + Log($">>> Java server started on port {port}."); - if (!WaitForServer(port)) + return new JavaServer(ports, process); + } + catch (Exception) { process.Kill(entireProcessTree: true); - KillProcessesOnPorts(ports); - - throw new InvalidOperationException( - $"Failed to wait for the server to start (can't connect the client on port {port}). Check logs for details."); + throw; } - - Log($">>> Java server started on port {port}."); - - return new JavaServer(ports, process); } private static Process CreateProcess(string gradleCommand, IDictionary<string, string?> env) @@ -331,5 +328,32 @@ namespace Apache.Ignite.Tests using var process = Process.Start(psi); process?.WaitForExit(); } + + private static int[] GetUnusedPorts(int count) + { + var ports = new int[count]; + var listeners = new List<TcpListener>(); + + try + { + for (var i = 0; i < count; i++) + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listeners.Add(listener); + + listener.Start(); + ports[i] = ((IPEndPoint)listener.LocalEndpoint).Port; + } + + return ports; + } + finally + { + foreach (var listener in listeners) + { + listener.Stop(); + } + } + } } }