aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2020-01-16 15:13:46 +0100
committerSonarTech <sonartech@sonarsource.com>2020-02-20 20:46:15 +0100
commitaccb6a680a3f6d5bd6925067ba4e9f09dd3d1a57 (patch)
treeac7cf992c884a4ab37f968b272210b2ba5a05c7b /server
parente588269aa85389e14c5138a7a95ddb24ed16934d (diff)
downloadsonarqube-accb6a680a3f6d5bd6925067ba4e9f09dd3d1a57.tar.gz
sonarqube-accb6a680a3f6d5bd6925067ba4e9f09dd3d1a57.zip
SONAR-12955 support IPv6 in cluster properties
and consistently valid adresses
Diffstat (limited to 'server')
-rw-r--r--server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java147
-rw-r--r--server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java2
-rw-r--r--server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java340
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java13
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java61
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java12
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java81
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberBuilderTest.java2
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java6
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java6
-rw-r--r--server/sonar-webserver/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java2
11 files changed, 520 insertions, 152 deletions
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
index 22dfe4d5d2d..a40727fa710 100644
--- a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
+++ b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
@@ -19,26 +19,31 @@
*/
package org.sonar.application.config;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
+import com.google.common.net.HostAndPort;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
-import org.apache.commons.lang.StringUtils;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
import org.slf4j.LoggerFactory;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.process.MessageException;
import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties.Property;
import org.sonar.process.Props;
import org.sonar.process.cluster.NodeType;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
+import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;
-import static org.apache.commons.lang.StringUtils.isBlank;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
import static org.sonar.process.ProcessProperties.Property.AUTH_JWT_SECRET;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_HZ_HOSTS;
@@ -49,6 +54,7 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_WEB_STARTUP_LEADER;
import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
import static org.sonar.process.ProcessProperties.Property.SEARCH_HOST;
+import static org.sonar.process.ProcessProperties.Property.SEARCH_PORT;
public class ClusterSettings implements Consumer<Props> {
@@ -71,16 +77,23 @@ public class ClusterSettings implements Consumer<Props> {
throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_STARTUP_LEADER.getKey()));
}
+ checkNodeSpecificProperties(props);
+ checkCommonProperties(props);
+ }
+
+ private void checkNodeSpecificProperties(Props props) {
NodeType nodeType = toNodeType(props);
switch (nodeType) {
case APPLICATION:
ensureNotH2(props);
- requireValue(props, AUTH_JWT_SECRET.getKey());
- ensureNotLoopbackAddresses(props, CLUSTER_HZ_HOSTS.getKey());
+ requireValue(props, AUTH_JWT_SECRET);
+ Set<AddressAndPort> hzNodes = parseHosts(CLUSTER_HZ_HOSTS, requireValue(props, CLUSTER_HZ_HOSTS));
+ ensureNotLoopbackAddresses(CLUSTER_HZ_HOSTS, hzNodes);
break;
case SEARCH:
- requireValue(props, SEARCH_HOST.getKey());
- ensureLocalButNotLoopbackAddress(props, SEARCH_HOST.getKey());
+ AddressAndPort searchHost = parseAndCheckHost(SEARCH_HOST, requireValue(props, SEARCH_HOST));
+ ensureLocalButNotLoopbackAddress(SEARCH_HOST, searchHost);
+ requireValue(props, SEARCH_PORT);
if (props.contains(CLUSTER_NODE_HZ_PORT.getKey())) {
LoggerFactory.getLogger(getClass()).warn("Property {} is ignored on search nodes since 7.2", CLUSTER_NODE_HZ_PORT.getKey());
}
@@ -88,13 +101,50 @@ public class ClusterSettings implements Consumer<Props> {
default:
throw new UnsupportedOperationException("Unknown value: " + nodeType);
}
- requireValue(props, CLUSTER_NODE_HOST.getKey());
- ensureLocalButNotLoopbackAddress(props, CLUSTER_NODE_HOST.getKey());
- ensureNotLoopbackAddresses(props, CLUSTER_SEARCH_HOSTS.getKey());
+ }
+
+ private void checkCommonProperties(Props props) {
+ AddressAndPort clusterNodeHost = parseAndCheckHost(CLUSTER_NODE_HOST, requireValue(props, CLUSTER_NODE_HOST));
+ ensureLocalButNotLoopbackAddress(CLUSTER_NODE_HOST, clusterNodeHost);
+ Set<AddressAndPort> searchHosts = parseHosts(CLUSTER_SEARCH_HOSTS, requireValue(props, CLUSTER_SEARCH_HOSTS));
+ ensureNotLoopbackAddresses(CLUSTER_SEARCH_HOSTS, searchHosts);
+ }
+
+ private Set<AddressAndPort> parseHosts(Property property, String value) {
+ Set<AddressAndPort> res = stream(value.split(","))
+ .filter(Objects::nonNull)
+ .map(String::trim)
+ .map(ClusterSettings::parseHost)
+ .collect(toSet());
+ checkValidHosts(property, res);
+ return res;
+ }
+
+ private void checkValidHosts(Property property, Set<AddressAndPort> addressAndPorts) {
+ List<String> invalidHosts = addressAndPorts.stream()
+ .map(AddressAndPort::getHost)
+ .filter(t -> !network.toInetAddress(t).isPresent())
+ .sorted()
+ .collect(toList());
+ if (!invalidHosts.isEmpty()) {
+ throw new MessageException(format("Address in property %s is not a valid address: %s",
+ property.getKey(), String.join(", ", invalidHosts)));
+ }
+ }
+
+ private static AddressAndPort parseHost(String value) {
+ HostAndPort hostAndPort = HostAndPort.fromString(value);
+ return new AddressAndPort(hostAndPort.getHost(), hostAndPort.hasPort() ? hostAndPort.getPort() : null);
+ }
+
+ private AddressAndPort parseAndCheckHost(Property property, String value) {
+ AddressAndPort addressAndPort = parseHost(value);
+ checkValidHosts(property, singleton(addressAndPort));
+ return addressAndPort;
}
private static NodeType toNodeType(Props props) {
- String nodeTypeValue = requireValue(props, CLUSTER_NODE_TYPE.getKey());
+ String nodeTypeValue = requireValue(props, CLUSTER_NODE_TYPE);
if (!NodeType.isValid(nodeTypeValue)) {
throw new MessageException(format("Invalid value for property %s: [%s], only [%s] are allowed", CLUSTER_NODE_TYPE.getKey(), nodeTypeValue,
Arrays.stream(NodeType.values()).map(NodeType::getValue).collect(joining(", "))));
@@ -102,46 +152,59 @@ public class ClusterSettings implements Consumer<Props> {
return NodeType.parse(nodeTypeValue);
}
- private static String requireValue(Props props, String key) {
+ private static String requireValue(Props props, Property property) {
+ String key = property.getKey();
String value = props.value(key);
- if (isBlank(value)) {
+ String trimmedValue = value == null ? null : value.trim();
+ if (trimmedValue == null || trimmedValue.isEmpty()) {
throw new MessageException(format("Property %s is mandatory", key));
}
- return value;
+ return trimmedValue;
}
private static void ensureNotH2(Props props) {
String jdbcUrl = props.value(JDBC_URL.getKey());
- if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) {
+ String trimmedJdbcUrl = jdbcUrl == null ? null : jdbcUrl.trim();
+ if (trimmedJdbcUrl == null || trimmedJdbcUrl.isEmpty() || trimmedJdbcUrl.startsWith("jdbc:h2:")) {
throw new MessageException("Embedded database is not supported in cluster mode");
}
}
- private void ensureNotLoopbackAddresses(Props props, String propertyKey) {
- stream(requireValue(props, propertyKey).split(","))
- .filter(StringUtils::isNotBlank)
- .map(StringUtils::trim)
- .map(s -> StringUtils.substringBefore(s, ":"))
- .forEach(ip -> {
- try {
- if (network.isLoopbackInetAddress(network.toInetAddress(ip))) {
- throw new MessageException(format("Property %s must not be a loopback address: %s", propertyKey, ip));
- }
- } catch (UnknownHostException e) {
- throw new MessageException(format("Property %s must not a valid address: %s [%s]", propertyKey, ip, e.getMessage()));
- }
- });
- }
-
- private void ensureLocalButNotLoopbackAddress(Props props, String propertyKey) {
- String propertyValue = props.nonNullValue(propertyKey).trim();
- try {
- InetAddress address = network.toInetAddress(propertyValue);
- if (!network.isLocalInetAddress(address) || network.isLoopbackInetAddress(address)) {
- throw new MessageException(format("Property %s must be a local non-loopback address: %s", propertyKey, propertyValue));
- }
- } catch (UnknownHostException | SocketException e) {
- throw new MessageException(format("Property %s must be a local non-loopback address: %s [%s]", propertyKey, propertyValue, e.getMessage()));
+ private void ensureNotLoopbackAddresses(Property property, Set<AddressAndPort> hostAndPorts) {
+ Set<AddressAndPort> loopbackAddresses = hostAndPorts.stream()
+ .filter(t -> network.isLoopback(t.getHost()))
+ .collect(MoreCollectors.toSet());
+ if (!loopbackAddresses.isEmpty()) {
+ throw new MessageException(format("Property %s must not contain a loopback address: %s", property.getKey(),
+ loopbackAddresses.stream().map(AddressAndPort::getHost).sorted().collect(Collectors.joining(", "))));
+ }
+ }
+
+ private void ensureLocalButNotLoopbackAddress(Property property, AddressAndPort addressAndPort) {
+ String host = addressAndPort.getHost();
+ if (!network.isLocal(host) || network.isLoopback(host)) {
+ throw new MessageException(format("Property %s must be a local non-loopback address: %s", property.getKey(), addressAndPort.getHost()));
+ }
+ }
+
+ private static class AddressAndPort {
+ private static final int NO_PORT = -1;
+
+ /** the host from setting, can be a hostname or an IP address */
+ private final String host;
+ private final int port;
+
+ private AddressAndPort(String host, @Nullable Integer port) {
+ this.host = host;
+ this.port = port == null ? NO_PORT : port;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public boolean hasPort() {
+ return port != NO_PORT;
}
}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
index 01aa75edc8d..9a914344aad 100644
--- a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
+++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
@@ -155,7 +155,7 @@ public class ClusterAppStateImplTest {
return new HazelcastMemberBuilder()
.setProcessId(ProcessId.COMPUTE_ENGINE)
.setNodeName("bar")
- .setPort(NetworkUtilsImpl.INSTANCE.getNextAvailablePort(loopback))
+ .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
.setNetworkInterface(loopback.getHostAddress())
.build();
}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
index 192f367bdac..0058ae54187 100644
--- a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
+++ b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
@@ -19,18 +19,29 @@
*/
package org.sonar.application.config;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.net.InetAddress;
-import org.junit.Before;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
import org.sonar.process.MessageException;
import org.sonar.process.NetworkUtils;
-import org.sonar.process.NetworkUtilsImpl;
+import static com.google.common.collect.ImmutableList.of;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.spy;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
import static org.sonar.process.ProcessId.ELASTICSEARCH;
@@ -43,23 +54,18 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
import static org.sonar.process.ProcessProperties.Property.SEARCH_HOST;
+@RunWith(DataProviderRunner.class)
public class ClusterSettingsTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private InetAddress nonLoopbackLocal = InetAddress.getLoopbackAddress();
- private NetworkUtils network = spy(NetworkUtilsImpl.INSTANCE);
-
- @Before
- public void setUp() throws Exception {
- when(network.isLocalInetAddress(nonLoopbackLocal)).thenReturn(true);
- when(network.isLoopbackInetAddress(nonLoopbackLocal)).thenReturn(false);
- }
+ private NetworkUtils network = Mockito.mock(NetworkUtils.class);
@Test
- public void test_isClusterEnabled() {
- TestAppSettings settings = newSettingsForAppNode().set(CLUSTER_ENABLED.getKey(), "true");
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void test_isClusterEnabled(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host).set(CLUSTER_ENABLED.getKey(), "true");
assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue();
settings = new TestAppSettings().set(CLUSTER_ENABLED.getKey(), "false");
@@ -78,11 +84,12 @@ public class ClusterSettingsTest {
}
@Test
- public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void getEnabledProcesses_returns_configured_processes_in_cluster_mode(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, WEB_SERVER);
- settings = newSettingsForSearchNode();
+ settings = newSettingsForSearchNode(host);
assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(ELASTICSEARCH);
}
@@ -110,8 +117,9 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_throws_MessageException_if_internal_property_for_startup_leader_is_configured() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_internal_property_for_startup_leader_is_configured(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.set("sonar.cluster.web.startupLeader", "true");
expectedException.expect(MessageException.class);
@@ -131,8 +139,9 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_throws_MessageException_if_h2_on_application_node() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_h2_on_application_node(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.set("sonar.jdbc.url", "jdbc:h2:mem");
expectedException.expect(MessageException.class);
@@ -142,8 +151,11 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_does_not_verify_h2_on_search_node() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_does_not_verify_h2_on_search_node(String host) {
+ mockValidHost(host);
+ mockLocalNonLoopback(host);
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set("sonar.jdbc.url", "jdbc:h2:mem");
// do not fail
@@ -151,8 +163,9 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_throws_MessageException_on_application_node_if_default_jdbc_url() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_on_application_node_if_default_jdbc_url(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.clearProperty(JDBC_URL.getKey());
expectedException.expect(MessageException.class);
@@ -168,82 +181,97 @@ public class ClusterSettingsTest {
}
@Test
- public void isLocalElasticsearchEnabled_returns_true_on_search_node() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void isLocalElasticsearchEnabled_returns_true_on_search_node(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
}
@Test
- public void shouldStartHazelcast_must_be_true_on_AppNode() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_must_be_true_on_AppNode(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isTrue();
}
@Test
- public void shouldStartHazelcast_must_be_false_on_SearchNode() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_must_be_false_on_SearchNode(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
}
@Test
- public void shouldStartHazelcast_must_be_false_when_cluster_not_activated() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_must_be_false_when_cluster_not_activated(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set(CLUSTER_ENABLED.getKey(), "false");
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
- settings = newSettingsForAppNode();
+ settings = newSettingsForAppNode(host);
settings.set(CLUSTER_ENABLED.getKey(), "false");
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
}
@Test
- public void isLocalElasticsearchEnabled_returns_true_for_a_application_node() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void isLocalElasticsearchEnabled_returns_true_for_a_application_node(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse();
}
@Test
- public void accept_throws_MessageException_if_searchHost_is_missing() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_searchHost_is_missing(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.clearProperty(SEARCH_HOST.getKey());
assertThatPropertyIsMandatory(settings, SEARCH_HOST.getKey());
}
@Test
- public void accept_throws_MessageException_if_searchHost_is_empty() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_searchHost_is_empty(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set(SEARCH_HOST.getKey(), "");
assertThatPropertyIsMandatory(settings, SEARCH_HOST.getKey());
}
@Test
- public void accept_throws_MessageException_on_app_node_if_clusterHosts_is_missing() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_on_app_node_if_clusterHosts_is_missing(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.clearProperty(CLUSTER_HZ_HOSTS.getKey());
assertThatPropertyIsMandatory(settings, CLUSTER_HZ_HOSTS.getKey());
}
@Test
- public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_clusterSearchHosts_is_missing(String host) {
+ mockValidHost(host);
+ mockLocalNonLoopback(host);
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.clearProperty(CLUSTER_SEARCH_HOSTS.getKey());
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS.getKey());
}
@Test
- public void accept_throws_MessageException_if_clusterSearchHosts_is_empty() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_clusterSearchHosts_is_empty(String host) {
+ mockValidHost(host);
+ mockLocalNonLoopback(host);
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set(CLUSTER_SEARCH_HOSTS.getKey(), "");
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS.getKey());
}
@Test
- public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.clearProperty("sonar.auth.jwtBase64Hs256Secret");
assertThatPropertyIsMandatory(settings, "sonar.auth.jwtBase64Hs256Secret");
}
@@ -255,14 +283,205 @@ public class ClusterSettingsTest {
}
@Test
- public void shouldStartHazelcast_should_return_false_on_SearchNode() {
- assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForSearchNode())).isFalse();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_should_return_false_on_SearchNode(String host) {
+ assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForSearchNode(host))).isFalse();
}
+ @Test
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_should_return_true_on_AppNode(String host) {
+ assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForAppNode(host))).isTrue();
+ }
@Test
- public void shouldStartHazelcast_should_return_true_on_AppNode() {
- assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForAppNode())).isTrue();
+ public void validate_host_in_any_cluster_property_of_APP_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "application")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_HZ_HOSTS.getKey(), "remote_hz_host_1,remote_hz_host_2")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar")
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde");
+
+ verifyHostIsChecked(settings, of("hz_host"), "Address in property sonar.cluster.node.host is not a valid address: hz_host");
+ verifyHostIsChecked(settings, of("remote_hz_host_1"), "Address in property sonar.cluster.hosts is not a valid address: remote_hz_host_1");
+ verifyHostIsChecked(settings, of("remote_hz_host_2"), "Address in property sonar.cluster.hosts is not a valid address: remote_hz_host_2");
+ verifyHostIsChecked(settings,
+ of("remote_hz_host_1", "remote_hz_host_2"),
+ "Address in property sonar.cluster.hosts is not a valid address: remote_hz_host_1, remote_hz_host_2");
+ verifyHostIsChecked(settings, of("remote_search_host_1"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_1");
+ verifyHostIsChecked(settings, of("remote_search_host_2"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_2");
+ verifyHostIsChecked(settings,
+ of("remote_search_host_1", "remote_search_host_2"),
+ "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_1, remote_search_host_2");
+ }
+
+ @Test
+ public void validate_host_resolved_in_any_cluster_property_of_SEARCH_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "search")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(SEARCH_HOST.getKey(), "search_host");
+
+ verifyHostIsChecked(settings, of("hz_host"), "Address in property sonar.cluster.node.host is not a valid address: hz_host");
+ verifyHostIsChecked(settings, of("remote_search_host_1"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_1");
+ verifyHostIsChecked(settings, of("remote_search_host_2"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_2");
+ verifyHostIsChecked(settings, of("search_host"), "Address in property sonar.search.host is not a valid address: search_host");
+ }
+
+ private void verifyHostIsChecked(TestAppSettings settings, Collection<String> invalidHosts, String expectedMessage) {
+ reset(network);
+ mockAllHostsValidBut(invalidHosts);
+ mockLocalNonLoopback("hz_host", "search_host");
+ assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps()))
+ .isInstanceOf(MessageException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ @Test
+ public void ensure_no_loopback_host_in_properties_of_APP_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "application")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_HZ_HOSTS.getKey(), "remote_hz_host_1,remote_hz_host_2")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar")
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde");
+
+ verifyLoopbackChecked(settings, of("hz_host"), "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ verifyLoopbackChecked(settings, of("remote_search_host_1"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1");
+ verifyLoopbackChecked(settings, of("remote_search_host_2"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_2");
+ verifyLoopbackChecked(settings,
+ of("remote_search_host_1", "remote_search_host_2"),
+ "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1, remote_search_host_2");
+ verifyLoopbackChecked(settings, of("remote_hz_host_1"), "Property sonar.cluster.hosts must not contain a loopback address: remote_hz_host_1");
+ verifyLoopbackChecked(settings, of("remote_hz_host_2"), "Property sonar.cluster.hosts must not contain a loopback address: remote_hz_host_2");
+ verifyLoopbackChecked(settings,
+ of("remote_hz_host_1", "remote_hz_host_2"),
+ "Property sonar.cluster.hosts must not contain a loopback address: remote_hz_host_1, remote_hz_host_2");
+ }
+
+ @Test
+ public void ensure_no_loopback_host_in_properties_of_SEARCH_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "search")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(SEARCH_HOST.getKey(), "search_host");
+
+ verifyLoopbackChecked(settings, of("hz_host"), "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ verifyLoopbackChecked(settings, of("search_host"), "Property sonar.search.host must be a local non-loopback address: search_host");
+ verifyLoopbackChecked(settings, of("remote_search_host_1"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1");
+ verifyLoopbackChecked(settings, of("remote_search_host_2"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_2");
+ verifyLoopbackChecked(settings,
+ of("remote_search_host_1", "remote_search_host_2"),
+ "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1, remote_search_host_2");
+ }
+
+ private void verifyLoopbackChecked(TestAppSettings settings, Collection<String> hosts, String expectedMessage) {
+ reset(network);
+ mockAllHostsValid();
+ mockLocalNonLoopback("hz_host", "search_host");
+ // will overwrite above move if necessary
+ hosts.forEach(this::mockLoopback);
+ assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps()))
+ .isInstanceOf(MessageException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ @Test
+ public void ensure_HZ_HOST_is_local_non_loopback_in_properties_of_APP_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "application")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_HZ_HOSTS.getKey(), "remote_hz_host_1,remote_hz_host_2")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar")
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde");
+
+ verifyLocalChecked(settings, "hz_host", "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ }
+
+ @Test
+ public void ensure_HZ_HOST_and_SEARCH_HOST_are_local_non_loopback_in_properties_of_SEARCH_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "search")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(SEARCH_HOST.getKey(), "search_host");
+
+ verifyLocalChecked(settings, "hz_host", "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ verifyLocalChecked(settings, "search_host", "Property sonar.search.host must be a local non-loopback address: search_host");
+ }
+
+ private void verifyLocalChecked(TestAppSettings settings, String host, String expectedMessage) {
+ reset(network);
+ mockAllHostsValid();
+ mockLocalNonLoopback("hz_host", "search_host");
+ // will overwrite above move if necessary
+ mockAllNonLoopback();
+ mockNonLocal(host);
+ assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps()))
+ .isInstanceOf(MessageException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ private void mockAllNonLoopback() {
+ when(network.isLoopback(anyString())).thenReturn(false);
+ }
+
+ private void mockNonLocal(String search_host) {
+ when(network.isLocal(search_host)).thenReturn(false);
+ }
+
+ private void mockLoopback(String host) {
+ when(network.isLoopback(host)).thenReturn(true);
+ }
+
+ private void mockValidHost(String host) {
+ String unbracketedHost = host.startsWith("[") ? host.substring(1, host.length() - 1) : host;
+ when(network.toInetAddress(unbracketedHost)).thenReturn(Optional.of(InetAddress.getLoopbackAddress()));
+ }
+
+ public void mockAllHostsValid() {
+ when(network.toInetAddress(anyString())).thenReturn(Optional.of(InetAddress.getLoopbackAddress()));
+ }
+
+ public void mockAllHostsValidBut(Collection<String> hosts) {
+ when(network.toInetAddress(anyString()))
+ .thenAnswer((Answer<Optional<InetAddress>>) invocation -> {
+ Object arg = invocation.getArgument(0);
+ if (hosts.contains(arg)) {
+ return Optional.empty();
+ }
+ return Optional.of(InetAddress.getLoopbackAddress());
+ });
+ }
+
+ private void mockLocalNonLoopback(String host, String... otherhosts) {
+ Stream.concat(Stream.of(host), Arrays.stream(otherhosts))
+ .forEach(h -> {
+ String unbracketedHost = h.startsWith("[") ? h.substring(1, h.length() - 1) : h;
+ when(network.isLocal(unbracketedHost)).thenReturn(true);
+ when(network.isLoopback(unbracketedHost)).thenReturn(false);
+ });
+
+ }
+
+ @DataProvider
+ public static Object[][] validIPv4andIPv6Addresses() {
+ return new Object[][] {
+ {"10.150.0.188"},
+ {"[fe80::fde2:607e:ae56:e636]"},
+ };
}
private void assertThatPropertyIsMandatory(TestAppSettings settings, String key) {
@@ -272,24 +491,25 @@ public class ClusterSettingsTest {
new ClusterSettings(network).accept(settings.getProps());
}
- private TestAppSettings newSettingsForAppNode() {
+ private TestAppSettings newSettingsForAppNode(String host) {
return new TestAppSettings()
.set(CLUSTER_ENABLED.getKey(), "true")
.set(CLUSTER_NODE_TYPE.getKey(), "application")
- .set(CLUSTER_NODE_HOST.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_HZ_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_SEARCH_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_NODE_HOST.getKey(), host)
+ .set(CLUSTER_HZ_HOSTS.getKey(), host)
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), host + ":9001")
.set("sonar.auth.jwtBase64Hs256Secret", "abcde")
.set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar");
}
- private TestAppSettings newSettingsForSearchNode() {
+ private TestAppSettings newSettingsForSearchNode(String host) {
return new TestAppSettings()
.set(CLUSTER_ENABLED.getKey(), "true")
.set(CLUSTER_NODE_TYPE.getKey(), "search")
- .set(CLUSTER_NODE_HOST.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_HZ_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_SEARCH_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
- .set(SEARCH_HOST.getKey(), nonLoopbackLocal.getHostAddress());
+ .set(CLUSTER_NODE_HOST.getKey(), host)
+ .set(CLUSTER_HZ_HOSTS.getKey(), host)
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), host + ":9001")
+ .set(SEARCH_HOST.getKey(), host);
}
+
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
index 901fff2b233..c40114467a8 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
@@ -21,14 +21,15 @@ package org.sonar.process;
import java.net.Inet4Address;
import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.function.Predicate;
public interface NetworkUtils {
- int getNextAvailablePort(InetAddress address);
+ int getNextLoopbackAvailablePort();
+
+ OptionalInt getNextAvailablePort(String hostOrAddress);
/**
* Identifying the localhost machine
@@ -44,11 +45,11 @@ public interface NetworkUtils {
* If text value references an IPv4 or IPv6 address, then DNS is
* not used.
*/
- InetAddress toInetAddress(String hostOrAddress) throws UnknownHostException;
+ Optional<InetAddress> toInetAddress(String hostOrAddress);
- boolean isLocalInetAddress(InetAddress address) throws SocketException;
+ boolean isLocal(String hostOrAddress);
- boolean isLoopbackInetAddress(InetAddress address);
+ boolean isLoopback(String hostOrAddress);
/**
* Returns the machine {@link InetAddress} that matches the specified
diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java
index d2e6ed1ecdc..dab2f97ed2f 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java
@@ -30,22 +30,36 @@ import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Predicate;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.lang.String.format;
public class NetworkUtilsImpl implements NetworkUtils {
- public static final NetworkUtils INSTANCE = new NetworkUtilsImpl();
private static final Set<Integer> PORTS_ALREADY_ALLOCATED = new HashSet<>();
private static final int PORT_MAX_TRIES = 50;
+ private static final Logger LOG = Loggers.get(NetworkUtilsImpl.class);
+
+ public static final NetworkUtils INSTANCE = new NetworkUtilsImpl();
NetworkUtilsImpl() {
// prevent instantiation
}
@Override
- public int getNextAvailablePort(InetAddress address) {
- return getNextAvailablePort(address, PortAllocator.INSTANCE);
+ public int getNextLoopbackAvailablePort() {
+ return getNextAvailablePort(InetAddress.getLoopbackAddress(), PortAllocator.INSTANCE);
+ }
+
+ @Override
+ public OptionalInt getNextAvailablePort(String hostOrAddress) {
+ return OptionalInt.of(toInetAddress(hostOrAddress)
+ .map(t -> getNextAvailablePort(t, PortAllocator.INSTANCE))
+ .orElseThrow(() -> new IllegalArgumentException(format("Can not resolve address %s", hostOrAddress))));
}
/**
@@ -84,32 +98,46 @@ public class NetworkUtilsImpl implements NetworkUtils {
@Override
public String getHostname() {
- String hostname;
try {
- hostname = InetAddress.getLocalHost().getHostName();
+ return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
- hostname = "unresolved hostname";
+ LOG.trace("Failed to get hostname", e);
+ return "unresolved hostname";
}
-
- return hostname;
}
@Override
- public InetAddress toInetAddress(String hostOrAddress) throws UnknownHostException {
- if (InetAddresses.isInetAddress(hostOrAddress)) {
- return InetAddresses.forString(hostOrAddress);
+ public Optional<InetAddress> toInetAddress(String hostOrAddress) {
+ try {
+ if (InetAddresses.isInetAddress(hostOrAddress)) {
+ return Optional.of(InetAddresses.forString(hostOrAddress));
+ }
+ return Optional.of(InetAddress.getByName(hostOrAddress));
+ } catch (UnknownHostException e) {
+ LOG.trace("toInetAddress({}) failed", hostOrAddress, e);
+ return Optional.empty();
}
- return InetAddress.getByName(hostOrAddress);
}
@Override
- public boolean isLocalInetAddress(InetAddress address) throws SocketException {
- return NetworkInterface.getByInetAddress(address) != null ;
+ public boolean isLocal(String hostOrAddress) {
+ try {
+ Optional<InetAddress> inetAddress = toInetAddress(hostOrAddress);
+ if (inetAddress.isPresent()) {
+ return NetworkInterface.getByInetAddress(inetAddress.get()) != null;
+ }
+ return false;
+ } catch (SocketException e) {
+ LOG.trace("isLocalInetAddress({}) failed", hostOrAddress, e);
+ return false;
+ }
}
@Override
- public boolean isLoopbackInetAddress(InetAddress address) {
- return address.isLoopbackAddress();
+ public boolean isLoopback(String hostOrAddress) {
+ return toInetAddress(hostOrAddress)
+ .filter(InetAddress::isLoopbackAddress)
+ .isPresent();
}
@Override
@@ -121,6 +149,7 @@ public class NetworkUtilsImpl implements NetworkUtils {
.filter(predicate)
.findFirst();
} catch (SocketException e) {
+ LOG.trace("getLocalInetAddress(Predicate<InetAddress>) failed", e);
throw new IllegalStateException("Can not retrieve network interfaces", e);
}
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
index bad9cb87aec..fa9a8657aa5 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
@@ -20,7 +20,6 @@
package org.sonar.process;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -33,9 +32,8 @@ import javax.annotation.Nullable;
import org.sonar.core.extension.CoreExtension;
import org.sonar.core.extension.ServiceLoaderWrapper;
-import static java.lang.String.format;
-
import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
/**
* Constants shared by search, web server and app processes.
@@ -200,11 +198,9 @@ public class ProcessProperties {
String port = props.value(portPropertyKey);
if ("0".equals(port)) {
String address = props.nonNullValue(addressPropertyKey);
- try {
- props.set(portPropertyKey, String.valueOf(NetworkUtilsImpl.INSTANCE.getNextAvailablePort(InetAddress.getByName(address))));
- } catch (UnknownHostException e) {
- throw new IllegalStateException("Cannot resolve address [" + address + "] set by property [" + addressPropertyKey + "]", e);
- }
+ int allocatedPort = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(address)
+ .orElseThrow(() -> new IllegalStateException("Cannot resolve address [" + address + "] set by property [" + addressPropertyKey + "]"));
+ props.set(portPropertyKey, String.valueOf(allocatedPort));
}
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java
index b54aa75e65a..86614ca00e3 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java
@@ -21,6 +21,7 @@ package org.sonar.process;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
@@ -30,6 +31,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeThat;
@@ -42,7 +44,8 @@ public class NetworkUtilsImplTest {
@Test
public void getNextAvailablePort_returns_a_port() throws Exception {
- int port = underTest.getNextAvailablePort(InetAddress.getLocalHost());
+ String localhost = InetAddress.getLocalHost().getHostName();
+ int port = underTest.getNextAvailablePort(localhost).getAsInt();
assertThat(port)
.isGreaterThan(1_023)
.isLessThanOrEqualTo(65_535);
@@ -50,10 +53,11 @@ public class NetworkUtilsImplTest {
@Test
public void getNextAvailablePort_does_not_return_twice_the_same_port() throws Exception {
+ String localhost = InetAddress.getLocalHost().getHostName();
Set<Integer> ports = new HashSet<>(Arrays.asList(
- underTest.getNextAvailablePort(InetAddress.getLocalHost()),
- underTest.getNextAvailablePort(InetAddress.getLocalHost()),
- underTest.getNextAvailablePort(InetAddress.getLocalHost())));
+ underTest.getNextAvailablePort(localhost).getAsInt(),
+ underTest.getNextAvailablePort(localhost).getAsInt(),
+ underTest.getNextAvailablePort(localhost).getAsInt()));
assertThat(ports).hasSize(3);
}
@@ -69,6 +73,17 @@ public class NetworkUtilsImplTest {
}
@Test
+ public void getHostname_returns_hostname_of_localhost_otherwise_a_constant() {
+ try {
+ InetAddress localHost = InetAddress.getLocalHost();
+ assertThat(underTest.getHostname()).isEqualTo(localHost.getHostName());
+ } catch (UnknownHostException e) {
+ // no localhost on host running the UT
+ assertThat(underTest.getHostname()).isEqualTo("unresolved hostname");
+ }
+ }
+
+ @Test
public void getLocalInetAddress_filters_local_addresses() {
InetAddress address = underTest.getLocalInetAddress(InetAddress::isLoopbackAddress).get();
assertThat(address.isLoopbackAddress()).isTrue();
@@ -81,20 +96,64 @@ public class NetworkUtilsImplTest {
}
@Test
- public void toInetAddress_supports_host_names() throws Exception {
- assertThat(underTest.toInetAddress("localhost")).isNotNull();
+ public void toInetAddress_supports_host_names() {
+ assertThat(underTest.toInetAddress("localhost")).isNotEmpty();
// do not test values that require DNS calls. Build must support offline mode.
}
@Test
- public void toInetAddress_supports_ipv4() throws Exception {
- assertThat(underTest.toInetAddress("1.2.3.4")).isNotNull();
+ public void toInetAddress_supports_ipv4() {
+ assertThat(underTest.toInetAddress("1.2.3.4")).isNotEmpty();
+ }
+
+ @Test
+ public void toInetAddress_supports_ipv6() {
+ assertThat(underTest.toInetAddress("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb")).isNotEmpty();
+ assertThat(underTest.toInetAddress("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]")).isNotEmpty();
}
@Test
- public void toInetAddress_supports_ipv6() throws Exception {
- assertThat(underTest.toInetAddress("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb")).isNotNull();
- assertThat(underTest.toInetAddress("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]")).isNotNull();
+ public void toInetAddress_returns_empty_on_unvalid_IP_and_hostname() {
+ assertThat(underTest.toInetAddress(randomAlphabetic(32))).isEmpty();
+ }
+
+ @Test
+ public void isLoopback_returns_true_on_loopback_address_or_host() {
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+
+ assertThat(underTest.isLoopback(loopback.getHostAddress())).isTrue();
+ assertThat(underTest.isLoopback(loopback.getHostName())).isTrue();
}
+ @Test
+ public void isLoopback_returns_true_on_localhost_address_or_host_if_loopback() {
+ try {
+ InetAddress localHost = InetAddress.getLocalHost();
+ boolean isLoopback = localHost.isLoopbackAddress();
+ assertThat(underTest.isLoopback(localHost.getHostAddress())).isEqualTo(isLoopback);
+ assertThat(underTest.isLoopback(localHost.getHostName())).isEqualTo(isLoopback);
+ } catch (UnknownHostException e) {
+ // ignore, host running the test has no localhost
+ }
+ }
+
+ @Test
+ public void isLocal_returns_true_on_loopback_address_or_host() {
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+
+ assertThat(underTest.isLocal(loopback.getHostAddress())).isTrue();
+ assertThat(underTest.isLocal(loopback.getHostName())).isTrue();
+ }
+
+ @Test
+ public void isLocal_returns_true_on_localhost_address_or_host() {
+ try {
+ InetAddress localHost = InetAddress.getLocalHost();
+
+ assertThat(underTest.isLocal(localHost.getHostAddress())).isTrue();
+ assertThat(underTest.isLocal(localHost.getHostName())).isTrue();
+ } catch (UnknownHostException e) {
+ // ignore, host running the test has no localhost
+ }
+ }
}
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 6df8aeffe6e..48d74b6971e 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
@@ -49,7 +49,7 @@ public class HazelcastMemberBuilderTest {
HazelcastMember member = underTest
.setProcessId(ProcessId.COMPUTE_ENGINE)
.setNodeName("bar")
- .setPort(NetworkUtilsImpl.INSTANCE.getNextAvailablePort(loopback))
+ .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
.setNetworkInterface(loopback.getHostAddress())
.build();
diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java
index 9e574e703a0..f96bb55a985 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/HazelcastMemberImplTest.java
@@ -55,9 +55,9 @@ public class HazelcastMemberImplTest {
@BeforeClass
public static void setUp() {
- int port1 = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(loopback);
- int port2 = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(loopback);
- int port3 = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(loopback);
+ int port1 = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
+ int port2 = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
+ int port3 = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
member1 = newHzMember(port1, port2, port3);
member2 = newHzMember(port2, port1, port3);
member3 = newHzMember(port3, port1, port2);
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java
index c2a5f05b04b..275f5dd6827 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java
@@ -111,7 +111,7 @@ public class EmbeddedDatabaseTest {
@Test
public void start_ignores_URL_to_create_database_and_uses_empty_username_and_password_when_then_are_not_set() throws IOException {
- int port = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(InetAddress.getLoopbackAddress());
+ int port = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
settings
.setProperty(PATH_DATA.getKey(), temporaryFolder.newFolder().getAbsolutePath())
.setProperty(JDBC_URL.getKey(), "jdbc url")
@@ -124,7 +124,7 @@ public class EmbeddedDatabaseTest {
@Test
public void start_creates_db_and_adds_tcp_listener() throws IOException {
- int port = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(InetAddress.getLoopbackAddress());
+ int port = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
settings
.setProperty(PATH_DATA.getKey(), temporaryFolder.newFolder().getAbsolutePath())
.setProperty(JDBC_URL.getKey(), "jdbc url")
@@ -142,7 +142,7 @@ public class EmbeddedDatabaseTest {
@Test
public void start_supports_in_memory_H2_JDBC_URL() throws IOException {
- int port = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(InetAddress.getLoopbackAddress());
+ int port = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
settings
.setProperty(PATH_DATA.getKey(), temporaryFolder.newFolder().getAbsolutePath())
.setProperty(JDBC_URL.getKey(), "jdbc:h2:mem:sonar")
diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
index e0cec4ad9ec..45252f13ca5 100644
--- a/server/sonar-webserver/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
+++ b/server/sonar-webserver/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
@@ -55,7 +55,7 @@ public class EmbeddedTomcatTest {
// start server on a random port
InetAddress address = InetAddress.getLoopbackAddress();
- int httpPort = NetworkUtilsImpl.INSTANCE.getNextAvailablePort(address);
+ int httpPort = NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort();
props.set("sonar.web.host", address.getHostAddress());
props.set("sonar.web.port", String.valueOf(httpPort));
EmbeddedTomcat tomcat = new EmbeddedTomcat(props);