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
This commit is contained in:
Andy Wilkinson 2023-06-15 11:47:44 +01:00
parent 63978978b4
commit 088006ff60
5 changed files with 87 additions and 63 deletions

View File

@ -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<AssertableApplicationContext> 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;
}
}

View File

@ -10,5 +10,11 @@
<cluster-connect-timeout-millis>60000</cluster-connect-timeout-millis>
</connection-retry>
</connection-strategy>
<network>
<cluster-members>
<address>${address}</address>
</cluster-members>
</network>
</hazelcast-client>

View File

@ -10,5 +10,9 @@
<cluster-connect-timeout-millis>60000</cluster-connect-timeout-millis>
</connection-retry>
</connection-strategy>
<network>
<cluster-members>
<address>${address}</address>
</cluster-members>
</network>
</hazelcast-client>

View File

@ -4,3 +4,6 @@ hazelcast-client:
connection-strategy:
connection-retry:
cluster-connect-timeout-millis: 60000
network:
cluster-members:
- ${address}

View File

@ -4,3 +4,6 @@ hazelcast-client:
connection-strategy:
connection-retry:
cluster-connect-timeout-millis: 60000
network:
cluster-members:
- ${address}