From accb6a680a3f6d5bd6925067ba4e9f09dd3d1a57 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 16 Jan 2020 15:13:46 +0100 Subject: [PATCH] SONAR-12955 support IPv6 in cluster properties and consistently valid adresses --- .../application/config/ClusterSettings.java | 147 +++++--- .../cluster/ClusterAppStateImplTest.java | 2 +- .../config/ClusterSettingsTest.java | 340 ++++++++++++++---- .../java/org/sonar/process/NetworkUtils.java | 13 +- .../org/sonar/process/NetworkUtilsImpl.java | 61 +++- .../org/sonar/process/ProcessProperties.java | 12 +- .../sonar/process/NetworkUtilsImplTest.java | 81 ++++- .../hz/HazelcastMemberBuilderTest.java | 2 +- .../cluster/hz/HazelcastMemberImplTest.java | 6 +- .../platform/db/EmbeddedDatabaseTest.java | 6 +- .../sonar/server/app/EmbeddedTomcatTest.java | 2 +- 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 { @@ -71,16 +77,23 @@ public class ClusterSettings implements Consumer { 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 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 { 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 searchHosts = parseHosts(CLUSTER_SEARCH_HOSTS, requireValue(props, CLUSTER_SEARCH_HOSTS)); + ensureNotLoopbackAddresses(CLUSTER_SEARCH_HOSTS, searchHosts); + } + + private Set parseHosts(Property property, String value) { + Set 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 addressAndPorts) { + List 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 { 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 hostAndPorts) { + Set 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 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 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 hosts) { + when(network.toInetAddress(anyString())) + .thenAnswer((Answer>) 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 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 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 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 = 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) 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 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); } @@ -68,6 +72,17 @@ public class NetworkUtilsImplTest { assertThat(address.get().isLoopbackAddress()).isFalse(); } + @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(); @@ -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); -- 2.39.5