and consistently valid adressestags/8.2.0.32929
@@ -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; | |||
} | |||
} | |||
@@ -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(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} | |||
@@ -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); | |||
} | |||
@@ -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 | |||
} | |||
} | |||
} |
@@ -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(); | |||
@@ -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); |
@@ -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") |
@@ -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); |