From 088006ff60ed911cf0f28f01c7f23a4093ffde92 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 15 Jun 2023 11:47:44 +0100 Subject: [PATCH] Use ephemeral port for Hazelcast When Hazelcast is started on a fixed port and that port is already in use, it does not fail to start which makes the problem hard to detect. A symptom of the problem is that clients will not be able to connect and will either retry indefinitely or will timeout depending on their configuration. This commit updates the Hazelcast client tests to start the Hazelcast instance on an ephemeral port and to customize the client configuration with the instance's address before use. This should allow the client tests to work reliably in an environment where Hazelcast's default port is already in use. Closes gh-35903 --- ...HazelcastAutoConfigurationClientTests.java | 132 ++++++++++-------- .../hazelcast/hazelcast-client-instance.xml | 6 + .../hazelcast/hazelcast-client-specific.xml | 6 +- .../hazelcast/hazelcast-client-specific.yaml | 3 + .../hazelcast/hazelcast-client-specific.yml | 3 + 5 files changed, 87 insertions(+), 63 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java index 4b3d98440c4..193f1424146 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java @@ -16,10 +16,16 @@ package org.springframework.boot.autoconfigure.hazelcast; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.nio.file.Files; + import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; -import com.hazelcast.client.config.ClientConnectionStrategyConfig; -import com.hazelcast.client.config.ConnectionRetryConfig; import com.hazelcast.client.impl.clientside.HazelcastClientProxy; import com.hazelcast.config.Config; import com.hazelcast.core.Hazelcast; @@ -36,6 +42,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -52,9 +59,15 @@ class HazelcastAutoConfigurationClientTests { */ private static HazelcastInstance hazelcastServer; + private static String endpointAddress; + @BeforeAll static void init() { - hazelcastServer = Hazelcast.newHazelcastInstance(); + Config config = Config.load(); + config.getNetworkConfig().setPort(0); + hazelcastServer = Hazelcast.newHazelcastInstance(config); + InetSocketAddress inetSocketAddress = (InetSocketAddress) hazelcastServer.getLocalEndpoint().getSocketAddress(); + endpointAddress = inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort(); } @AfterAll @@ -69,73 +82,52 @@ class HazelcastAutoConfigurationClientTests { @Test void systemPropertyWithXml() { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.xml"); this.contextRunner - .withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY - + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.xml") + .withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY + "=" + config.getAbsolutePath()) .run(assertSpecificHazelcastClient("explicit-xml")); } @Test void systemPropertyWithYaml() { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml"); this.contextRunner - .withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY - + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml") + .withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY + "=" + config.getAbsolutePath()) .run(assertSpecificHazelcastClient("explicit-yaml")); } @Test void systemPropertyWithYml() { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.yml"); this.contextRunner - .withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY - + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yml") + .withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY + "=" + config.getAbsolutePath()) .run(assertSpecificHazelcastClient("explicit-yml")); } @Test - void explicitConfigFileWithXml() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=org/springframework/boot/autoconfigure/" - + "hazelcast/hazelcast-client-specific.xml") + void explicitConfigUrlWithXml() throws MalformedURLException { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.xml"); + this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL()) .run(assertSpecificHazelcastClient("explicit-xml")); } @Test - void explicitConfigFileWithYaml() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=org/springframework/boot/autoconfigure/" - + "hazelcast/hazelcast-client-specific.yaml") + void explicitConfigUrlWithYaml() throws MalformedURLException { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml"); + this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL()) .run(assertSpecificHazelcastClient("explicit-yaml")); } @Test - void explicitConfigFileWithYml() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=org/springframework/boot/autoconfigure/" - + "hazelcast/hazelcast-client-specific.yml") - .run(assertSpecificHazelcastClient("explicit-yml")); - } - - @Test - void explicitConfigUrlWithXml() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=classpath:org/springframework/" - + "boot/autoconfigure/hazelcast/hazelcast-client-specific.xml") - .run(assertSpecificHazelcastClient("explicit-xml")); - } - - @Test - void explicitConfigUrlWithYaml() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=classpath:org/springframework/" - + "boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml") - .run(assertSpecificHazelcastClient("explicit-yaml")); - } - - @Test - void explicitConfigUrlWithYml() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=classpath:org/springframework/" - + "boot/autoconfigure/hazelcast/hazelcast-client-specific.yml") + void explicitConfigUrlWithYml() throws MalformedURLException { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.yml"); + this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL()) .run(assertSpecificHazelcastClient("explicit-yml")); } @@ -156,28 +148,26 @@ class HazelcastAutoConfigurationClientTests { } @Test - void clientConfigWithInstanceNameCreatesClientIfNecessary() { + void clientConfigWithInstanceNameCreatesClientIfNecessary() throws MalformedURLException { assertThat(HazelcastClient.getHazelcastClientByName("spring-boot")).isNull(); - this.contextRunner - .withPropertyValues("spring.hazelcast.config=classpath:org/springframework/" - + "boot/autoconfigure/hazelcast/hazelcast-client-instance.xml") + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-instance.xml"); + this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL()) .run((context) -> assertThat(context).getBean(HazelcastInstance.class) .extracting(HazelcastInstance::getName) .isEqualTo("spring-boot")); } @Test - void autoConfiguredClientConfigUsesApplicationClassLoader() { - this.contextRunner - .withPropertyValues("spring.hazelcast.config=org/springframework/boot/autoconfigure/" - + "hazelcast/hazelcast-client-specific.xml") - .run((context) -> { - HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class); - assertThat(hazelcast).isInstanceOf(HazelcastClientProxy.class); - ClientConfig clientConfig = ((HazelcastClientProxy) hazelcast).getClientConfig(); - assertThat(clientConfig.getClassLoader()) - .isSameAs(context.getSourceApplicationContext().getClassLoader()); - }); + void autoConfiguredClientConfigUsesApplicationClassLoader() throws MalformedURLException { + File config = prepareConfiguration("src/test/resources/org/springframework/" + + "boot/autoconfigure/hazelcast/hazelcast-client-specific.xml"); + this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL()).run((context) -> { + HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class); + assertThat(hazelcast).isInstanceOf(HazelcastClientProxy.class); + ClientConfig clientConfig = ((HazelcastClientProxy) hazelcast).getClientConfig(); + assertThat(clientConfig.getClassLoader()).isSameAs(context.getSourceApplicationContext().getClassLoader()); + }); } private ContextConsumer assertSpecificHazelcastClient(String label) { @@ -193,6 +183,22 @@ class HazelcastAutoConfigurationClientTests { .anyMatch((e) -> e.equals(label)), "Label equals to " + label); } + private File prepareConfiguration(String input) { + File configFile = new File(input); + try { + String config = FileCopyUtils.copyToString(new FileReader(configFile)); + config = config.replace("${address}", endpointAddress); + System.out.println(config); + File outputFile = new File(Files.createTempDirectory(getClass().getSimpleName()).toFile(), + configFile.getName()); + FileCopyUtils.copy(config, new FileWriter(outputFile)); + return outputFile; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + @Configuration(proxyBeanMethods = false) static class HazelcastServerAndClientConfig { @@ -203,8 +209,10 @@ class HazelcastAutoConfigurationClientTests { @Bean ClientConfig clientConfig() { - return new ClientConfig().setConnectionStrategyConfig(new ClientConnectionStrategyConfig() - .setConnectionRetryConfig(new ConnectionRetryConfig().setClusterConnectTimeoutMillis(60000))); + ClientConfig config = new ClientConfig(); + config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000); + config.getNetworkConfig().getAddresses().add(endpointAddress); + return config; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-instance.xml b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-instance.xml index 855e92086cd..750a658d798 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-instance.xml +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-instance.xml @@ -10,5 +10,11 @@ 60000 + + + +
${address}
+
+
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.xml b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.xml index 6210b807e84..1bd9e4182a4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.xml +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.xml @@ -10,5 +10,9 @@ 60000 - + + +
${address}
+
+
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml index 285d1fe683d..8e8589e67b4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yaml @@ -4,3 +4,6 @@ hazelcast-client: connection-strategy: connection-retry: cluster-connect-timeout-millis: 60000 + network: + cluster-members: + - ${address} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yml b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yml index cd831510b81..d1ca0670af7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yml +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-client-specific.yml @@ -4,3 +4,6 @@ hazelcast-client: connection-strategy: connection-retry: cluster-connect-timeout-millis: 60000 + network: + cluster-members: + - ${address}