aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSteve Marion <steve.marion@sonarsource.com>2024-10-23 17:13:38 +0200
committerLukasz Jarocki <lukasz.jarocki@sonarsource.com>2025-02-28 09:57:46 +0100
commitd782f35cfeae338e2dbf3e039cd04166b949a1d3 (patch)
tree3bd8a47a25ad462b88368b12263d7d6f87fe966e /server
parent64bef80cfa7b92dd7ba99bb40b464196ff3cdd0a (diff)
downloadsonarqube-d782f35cfeae338e2dbf3e039cd04166b949a1d3.tar.gz
sonarqube-d782f35cfeae338e2dbf3e039cd04166b949a1d3.zip
SONAR-23456 fix sonar.cluster.hosts not supporting default port on ipv6 addresses
Diffstat (limited to 'server')
-rw-r--r--server/sonar-process/build.gradle1
-rw-r--r--server/sonar-process/src/it/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderIT.java111
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberBuilder.java20
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderTest.java147
4 files changed, 183 insertions, 96 deletions
diff --git a/server/sonar-process/build.gradle b/server/sonar-process/build.gradle
index 613083e5d6e..543bca3f05b 100644
--- a/server/sonar-process/build.gradle
+++ b/server/sonar-process/build.gradle
@@ -30,6 +30,7 @@ dependencies {
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.hamcrest:hamcrest'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
+ testImplementation 'org.junit.jupiter:junit-jupiter-params'
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.awaitility:awaitility'
testImplementation project(':sonar-testing-harness')
diff --git a/server/sonar-process/src/it/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderIT.java b/server/sonar-process/src/it/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderIT.java
new file mode 100644
index 00000000000..fddb17aad1c
--- /dev/null
+++ b/server/sonar-process/src/it/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderIT.java
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.cluster.hz;
+
+import java.net.InetAddress;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.sonar.process.NetworkUtilsImpl;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+
+@Timeout(60)
+class HazelcastMemberBuilderIT {
+
+ // use loopback for support of offline builds
+ private final InetAddress loopback = InetAddress.getLoopbackAddress();
+
+ @Test
+ void build_tcp_ip_member_hostaddress() {
+ HazelcastMember member = new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP)
+ .setMembers(loopback.getHostAddress())
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .setNodeName("bar")
+ .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
+ .setNetworkInterface(loopback.getHostAddress())
+ .build();
+
+ assertThat(member.getUuid()).isNotNull();
+ assertThat(member.getClusterTime()).isPositive();
+ assertThat(member.getCluster().getMembers()).hasSize(1);
+ assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
+
+ assertThat(member.getAtomicReference("baz")).isNotNull();
+ assertThat(member.getLock("baz")).isNotNull();
+ assertThat(member.getReplicatedMap("baz")).isNotNull();
+
+ member.close();
+ }
+
+ @Test
+ void build_tcp_ip_member_hostname() {
+ HazelcastMember member = new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP)
+ .setMembers(loopback.getHostName())
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .setNodeName("bar")
+ .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
+ .setNetworkInterface(loopback.getHostAddress())
+ .build();
+
+ assertThat(member.getUuid()).isNotNull();
+ assertThat(member.getClusterTime()).isPositive();
+ assertThat(member.getCluster().getMembers()).hasSize(1);
+ assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
+
+ assertThat(member.getAtomicReference("baz")).isNotNull();
+ assertThat(member.getLock("baz")).isNotNull();
+ assertThat(member.getReplicatedMap("baz")).isNotNull();
+
+ member.close();
+ }
+
+ @Test
+ void build_kubernetes_member() {
+ HazelcastMember member = new HazelcastMemberBuilder(JoinConfigurationType.KUBERNETES)
+ .setMembers(loopback.getHostAddress())
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .setNodeName("bar")
+ .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
+ .setNetworkInterface(loopback.getHostAddress())
+ .build();
+
+ assertThat(member.getUuid()).isNotNull();
+ assertThat(member.getClusterTime()).isPositive();
+ assertThat(member.getCluster().getMembers()).hasSize(1);
+ assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
+
+ assertThat(member.getAtomicReference("baz")).isNotNull();
+ assertThat(member.getLock("baz")).isNotNull();
+ assertThat(member.getReplicatedMap("baz")).isNotNull();
+
+ member.close();
+ }
+
+ @Test
+ void fail_if_elasticsearch_process() {
+ var builder = new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP);
+ assertThatThrownBy(() -> builder.setProcessId(ProcessId.ELASTICSEARCH))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Hazelcast must not be enabled on Elasticsearch node");
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberBuilder.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberBuilder.java
index 2d0edcef910..0ffeec39e74 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberBuilder.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/HazelcastMemberBuilder.java
@@ -19,6 +19,7 @@
*/
package org.sonar.process.cluster.hz;
+import com.google.common.net.HostAndPort;
import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.MemberAttributeConfig;
@@ -29,7 +30,6 @@ import java.util.stream.Stream;
import org.sonar.process.ProcessId;
import org.sonar.process.cluster.hz.HazelcastMember.Attribute;
-import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HZ_PORT;
@@ -100,16 +100,19 @@ public class HazelcastMemberBuilder {
joinConfig.getAwsConfig().setEnabled(false);
joinConfig.getMulticastConfig().setEnabled(false);
+ final int defaultHzNodePort = Integer.parseInt(CLUSTER_NODE_HZ_PORT.getDefaultValue());
if (KUBERNETES.equals(type)) {
joinConfig.getKubernetesConfig().setEnabled(true)
.setProperty("service-dns", requireNonNull(members, "Service DNS is missing"))
- .setProperty("service-port", CLUSTER_NODE_HZ_PORT.getDefaultValue());
+ .setProperty("service-port", String.valueOf(defaultHzNodePort));
} else {
List<String> addressesWithDefaultPorts = Stream.of(this.members.split(","))
- .filter(host -> !host.isBlank())
- .map(String::trim)
- .map(HazelcastMemberBuilder::applyDefaultPortToHost)
- .toList();
+ .filter(host -> !host.isBlank())
+ .map(String::trim)
+ .map(HostAndPort::fromString)
+ .map(parsedHost -> parsedHost.withDefaultPort(defaultHzNodePort))
+ .map(HostAndPort::toString)
+ .toList();
joinConfig.getTcpIpConfig().setEnabled(true);
joinConfig.getTcpIpConfig().setMembers(requireNonNull(addressesWithDefaultPorts, "Members are missing"));
}
@@ -136,9 +139,4 @@ public class HazelcastMemberBuilder {
return new HazelcastMemberImpl(Hazelcast.newHazelcastInstance(config));
}
-
- private static String applyDefaultPortToHost(String host) {
- return host.contains(":") ? host : format("%s:%s", host, CLUSTER_NODE_HZ_PORT.getDefaultValue());
- }
-
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderTest.java
index b76089729c9..fd2e6395866 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderTest.java
@@ -19,97 +19,74 @@
*/
package org.sonar.process.cluster.hz;
-import java.net.InetAddress;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.sonar.process.NetworkUtilsImpl;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.InterfacesConfig;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.config.NetworkConfig;
+import com.hazelcast.config.TcpIpConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-public class HazelcastMemberBuilderTest {
-
- @Rule
- public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
- // use loopback for support of offline builds
- private final InetAddress loopback = InetAddress.getLoopbackAddress();
-
- @Test
- public void build_tcp_ip_member_hostaddress() {
- HazelcastMember member = new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP)
- .setMembers(loopback.getHostAddress())
- .setProcessId(ProcessId.COMPUTE_ENGINE)
- .setNodeName("bar")
- .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
- .setNetworkInterface(loopback.getHostAddress())
- .build();
-
- assertThat(member.getUuid()).isNotNull();
- assertThat(member.getClusterTime()).isPositive();
- assertThat(member.getCluster().getMembers()).hasSize(1);
- assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
-
- assertThat(member.getAtomicReference("baz")).isNotNull();
- assertThat(member.getLock("baz")).isNotNull();
- assertThat(member.getReplicatedMap("baz")).isNotNull();
-
- member.close();
- }
-
- @Test
- public void build_tcp_ip_member_hostname() {
- HazelcastMember member = new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP)
- .setMembers(loopback.getHostName())
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+
+
+@Timeout(60)
+class HazelcastMemberBuilderTest {
+
+ @CsvSource({
+ "::1,[::1]:9003",
+ "[::1],[::1]:9003",
+ "[::1]:5000,[::1]:5000",
+ "127.0.0.1,127.0.0.1:9003",
+ "127.0.0.1:5000,127.0.0.1:5000"
+ })
+ @ParameterizedTest
+ void add_default_port_to_member(String memberParam, String memberAddress) {
+ try (MockedStatic<Hazelcast> mockedHazelcast = mockStatic(Hazelcast.class)) {
+ ArgumentCaptor<Config> configCaptor = forClass(Config.class);
+ mockedHazelcast.when(() -> Hazelcast.newHazelcastInstance(configCaptor.capture()))
+ .thenReturn(mock(HazelcastInstance.class));
+
+ int defaultPort = Integer.parseInt(ProcessProperties.Property.CLUSTER_NODE_HZ_PORT.getDefaultValue());
+ new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP)
+ .setMembers(memberParam)
.setProcessId(ProcessId.COMPUTE_ENGINE)
.setNodeName("bar")
- .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
- .setNetworkInterface(loopback.getHostAddress())
+ .setPort(defaultPort)
+ .setNetworkInterface("127.0.0.1")
.build();
- assertThat(member.getUuid()).isNotNull();
- assertThat(member.getClusterTime()).isPositive();
- assertThat(member.getCluster().getMembers()).hasSize(1);
- assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
-
- assertThat(member.getAtomicReference("baz")).isNotNull();
- assertThat(member.getLock("baz")).isNotNull();
- assertThat(member.getReplicatedMap("baz")).isNotNull();
-
- member.close();
- }
-
- @Test
- public void build_kubernetes_member() {
- HazelcastMember member = new HazelcastMemberBuilder(JoinConfigurationType.KUBERNETES)
- .setMembers(loopback.getHostAddress())
- .setProcessId(ProcessId.COMPUTE_ENGINE)
- .setNodeName("bar")
- .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
- .setNetworkInterface(loopback.getHostAddress())
- .build();
-
- assertThat(member.getUuid()).isNotNull();
- assertThat(member.getClusterTime()).isPositive();
- assertThat(member.getCluster().getMembers()).hasSize(1);
- assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
-
- assertThat(member.getAtomicReference("baz")).isNotNull();
- assertThat(member.getLock("baz")).isNotNull();
- assertThat(member.getReplicatedMap("baz")).isNotNull();
-
- member.close();
- }
-
- @Test
- public void fail_if_elasticsearch_process() {
- var builder = new HazelcastMemberBuilder(JoinConfigurationType.TCP_IP);
- assertThatThrownBy(() -> builder.setProcessId(ProcessId.ELASTICSEARCH))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Hazelcast must not be enabled on Elasticsearch node");
+ Config config = configCaptor.getValue();
+
+ assertThat(config.getNetworkConfig())
+ .extracting(NetworkConfig::getPort)
+ .isEqualTo(defaultPort);
+
+ assertThat(config.getNetworkConfig())
+ .extracting(NetworkConfig::getInterfaces)
+ .extracting(InterfacesConfig::getInterfaces, as(InstanceOfAssertFactories.COLLECTION))
+ .hasSize(1).element(0)
+ .isEqualTo("127.0.0.1");
+
+ assertThat(config.getNetworkConfig())
+ .extracting(NetworkConfig::getJoin)
+ .extracting(JoinConfig::getTcpIpConfig)
+ .extracting(TcpIpConfig::getMembers)
+ .asInstanceOf(InstanceOfAssertFactories.LIST)
+ .hasSize(1).element(0)
+ .isEqualTo(memberAddress);
+ }
}
}