aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-main
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2020-01-16 15:13:46 +0100
committerSonarTech <sonartech@sonarsource.com>2020-02-20 20:46:15 +0100
commitaccb6a680a3f6d5bd6925067ba4e9f09dd3d1a57 (patch)
treeac7cf992c884a4ab37f968b272210b2ba5a05c7b /server/sonar-main
parente588269aa85389e14c5138a7a95ddb24ed16934d (diff)
downloadsonarqube-accb6a680a3f6d5bd6925067ba4e9f09dd3d1a57.tar.gz
sonarqube-accb6a680a3f6d5bd6925067ba4e9f09dd3d1a57.zip
SONAR-12955 support IPv6 in cluster properties
and consistently valid adresses
Diffstat (limited to 'server/sonar-main')
-rw-r--r--server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java147
-rw-r--r--server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java2
-rw-r--r--server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java340
3 files changed, 386 insertions, 103 deletions
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
index 22dfe4d5d2d..a40727fa710 100644
--- a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
+++ b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
@@ -19,26 +19,31 @@
*/
package org.sonar.application.config;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
+import com.google.common.net.HostAndPort;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
-import org.apache.commons.lang.StringUtils;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
import org.slf4j.LoggerFactory;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.process.MessageException;
import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties.Property;
import org.sonar.process.Props;
import org.sonar.process.cluster.NodeType;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
+import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;
-import static org.apache.commons.lang.StringUtils.isBlank;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
import static org.sonar.process.ProcessProperties.Property.AUTH_JWT_SECRET;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_HZ_HOSTS;
@@ -49,6 +54,7 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.Property.CLUSTER_WEB_STARTUP_LEADER;
import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
import static org.sonar.process.ProcessProperties.Property.SEARCH_HOST;
+import static org.sonar.process.ProcessProperties.Property.SEARCH_PORT;
public class ClusterSettings implements Consumer<Props> {
@@ -71,16 +77,23 @@ public class ClusterSettings implements Consumer<Props> {
throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_STARTUP_LEADER.getKey()));
}
+ checkNodeSpecificProperties(props);
+ checkCommonProperties(props);
+ }
+
+ private void checkNodeSpecificProperties(Props props) {
NodeType nodeType = toNodeType(props);
switch (nodeType) {
case APPLICATION:
ensureNotH2(props);
- requireValue(props, AUTH_JWT_SECRET.getKey());
- ensureNotLoopbackAddresses(props, CLUSTER_HZ_HOSTS.getKey());
+ requireValue(props, AUTH_JWT_SECRET);
+ Set<AddressAndPort> hzNodes = parseHosts(CLUSTER_HZ_HOSTS, requireValue(props, CLUSTER_HZ_HOSTS));
+ ensureNotLoopbackAddresses(CLUSTER_HZ_HOSTS, hzNodes);
break;
case SEARCH:
- requireValue(props, SEARCH_HOST.getKey());
- ensureLocalButNotLoopbackAddress(props, SEARCH_HOST.getKey());
+ AddressAndPort searchHost = parseAndCheckHost(SEARCH_HOST, requireValue(props, SEARCH_HOST));
+ ensureLocalButNotLoopbackAddress(SEARCH_HOST, searchHost);
+ requireValue(props, SEARCH_PORT);
if (props.contains(CLUSTER_NODE_HZ_PORT.getKey())) {
LoggerFactory.getLogger(getClass()).warn("Property {} is ignored on search nodes since 7.2", CLUSTER_NODE_HZ_PORT.getKey());
}
@@ -88,13 +101,50 @@ public class ClusterSettings implements Consumer<Props> {
default:
throw new UnsupportedOperationException("Unknown value: " + nodeType);
}
- requireValue(props, CLUSTER_NODE_HOST.getKey());
- ensureLocalButNotLoopbackAddress(props, CLUSTER_NODE_HOST.getKey());
- ensureNotLoopbackAddresses(props, CLUSTER_SEARCH_HOSTS.getKey());
+ }
+
+ private void checkCommonProperties(Props props) {
+ AddressAndPort clusterNodeHost = parseAndCheckHost(CLUSTER_NODE_HOST, requireValue(props, CLUSTER_NODE_HOST));
+ ensureLocalButNotLoopbackAddress(CLUSTER_NODE_HOST, clusterNodeHost);
+ Set<AddressAndPort> searchHosts = parseHosts(CLUSTER_SEARCH_HOSTS, requireValue(props, CLUSTER_SEARCH_HOSTS));
+ ensureNotLoopbackAddresses(CLUSTER_SEARCH_HOSTS, searchHosts);
+ }
+
+ private Set<AddressAndPort> parseHosts(Property property, String value) {
+ Set<AddressAndPort> res = stream(value.split(","))
+ .filter(Objects::nonNull)
+ .map(String::trim)
+ .map(ClusterSettings::parseHost)
+ .collect(toSet());
+ checkValidHosts(property, res);
+ return res;
+ }
+
+ private void checkValidHosts(Property property, Set<AddressAndPort> addressAndPorts) {
+ List<String> invalidHosts = addressAndPorts.stream()
+ .map(AddressAndPort::getHost)
+ .filter(t -> !network.toInetAddress(t).isPresent())
+ .sorted()
+ .collect(toList());
+ if (!invalidHosts.isEmpty()) {
+ throw new MessageException(format("Address in property %s is not a valid address: %s",
+ property.getKey(), String.join(", ", invalidHosts)));
+ }
+ }
+
+ private static AddressAndPort parseHost(String value) {
+ HostAndPort hostAndPort = HostAndPort.fromString(value);
+ return new AddressAndPort(hostAndPort.getHost(), hostAndPort.hasPort() ? hostAndPort.getPort() : null);
+ }
+
+ private AddressAndPort parseAndCheckHost(Property property, String value) {
+ AddressAndPort addressAndPort = parseHost(value);
+ checkValidHosts(property, singleton(addressAndPort));
+ return addressAndPort;
}
private static NodeType toNodeType(Props props) {
- String nodeTypeValue = requireValue(props, CLUSTER_NODE_TYPE.getKey());
+ String nodeTypeValue = requireValue(props, CLUSTER_NODE_TYPE);
if (!NodeType.isValid(nodeTypeValue)) {
throw new MessageException(format("Invalid value for property %s: [%s], only [%s] are allowed", CLUSTER_NODE_TYPE.getKey(), nodeTypeValue,
Arrays.stream(NodeType.values()).map(NodeType::getValue).collect(joining(", "))));
@@ -102,46 +152,59 @@ public class ClusterSettings implements Consumer<Props> {
return NodeType.parse(nodeTypeValue);
}
- private static String requireValue(Props props, String key) {
+ private static String requireValue(Props props, Property property) {
+ String key = property.getKey();
String value = props.value(key);
- if (isBlank(value)) {
+ String trimmedValue = value == null ? null : value.trim();
+ if (trimmedValue == null || trimmedValue.isEmpty()) {
throw new MessageException(format("Property %s is mandatory", key));
}
- return value;
+ return trimmedValue;
}
private static void ensureNotH2(Props props) {
String jdbcUrl = props.value(JDBC_URL.getKey());
- if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) {
+ String trimmedJdbcUrl = jdbcUrl == null ? null : jdbcUrl.trim();
+ if (trimmedJdbcUrl == null || trimmedJdbcUrl.isEmpty() || trimmedJdbcUrl.startsWith("jdbc:h2:")) {
throw new MessageException("Embedded database is not supported in cluster mode");
}
}
- private void ensureNotLoopbackAddresses(Props props, String propertyKey) {
- stream(requireValue(props, propertyKey).split(","))
- .filter(StringUtils::isNotBlank)
- .map(StringUtils::trim)
- .map(s -> StringUtils.substringBefore(s, ":"))
- .forEach(ip -> {
- try {
- if (network.isLoopbackInetAddress(network.toInetAddress(ip))) {
- throw new MessageException(format("Property %s must not be a loopback address: %s", propertyKey, ip));
- }
- } catch (UnknownHostException e) {
- throw new MessageException(format("Property %s must not a valid address: %s [%s]", propertyKey, ip, e.getMessage()));
- }
- });
- }
-
- private void ensureLocalButNotLoopbackAddress(Props props, String propertyKey) {
- String propertyValue = props.nonNullValue(propertyKey).trim();
- try {
- InetAddress address = network.toInetAddress(propertyValue);
- if (!network.isLocalInetAddress(address) || network.isLoopbackInetAddress(address)) {
- throw new MessageException(format("Property %s must be a local non-loopback address: %s", propertyKey, propertyValue));
- }
- } catch (UnknownHostException | SocketException e) {
- throw new MessageException(format("Property %s must be a local non-loopback address: %s [%s]", propertyKey, propertyValue, e.getMessage()));
+ private void ensureNotLoopbackAddresses(Property property, Set<AddressAndPort> hostAndPorts) {
+ Set<AddressAndPort> loopbackAddresses = hostAndPorts.stream()
+ .filter(t -> network.isLoopback(t.getHost()))
+ .collect(MoreCollectors.toSet());
+ if (!loopbackAddresses.isEmpty()) {
+ throw new MessageException(format("Property %s must not contain a loopback address: %s", property.getKey(),
+ loopbackAddresses.stream().map(AddressAndPort::getHost).sorted().collect(Collectors.joining(", "))));
+ }
+ }
+
+ private void ensureLocalButNotLoopbackAddress(Property property, AddressAndPort addressAndPort) {
+ String host = addressAndPort.getHost();
+ if (!network.isLocal(host) || network.isLoopback(host)) {
+ throw new MessageException(format("Property %s must be a local non-loopback address: %s", property.getKey(), addressAndPort.getHost()));
+ }
+ }
+
+ private static class AddressAndPort {
+ private static final int NO_PORT = -1;
+
+ /** the host from setting, can be a hostname or an IP address */
+ private final String host;
+ private final int port;
+
+ private AddressAndPort(String host, @Nullable Integer port) {
+ this.host = host;
+ this.port = port == null ? NO_PORT : port;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public boolean hasPort() {
+ return port != NO_PORT;
}
}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
index 01aa75edc8d..9a914344aad 100644
--- a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
+++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
@@ -155,7 +155,7 @@ public class ClusterAppStateImplTest {
return new HazelcastMemberBuilder()
.setProcessId(ProcessId.COMPUTE_ENGINE)
.setNodeName("bar")
- .setPort(NetworkUtilsImpl.INSTANCE.getNextAvailablePort(loopback))
+ .setPort(NetworkUtilsImpl.INSTANCE.getNextLoopbackAvailablePort())
.setNetworkInterface(loopback.getHostAddress())
.build();
}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
index 192f367bdac..0058ae54187 100644
--- a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
+++ b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
@@ -19,18 +19,29 @@
*/
package org.sonar.application.config;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.net.InetAddress;
-import org.junit.Before;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
import org.sonar.process.MessageException;
import org.sonar.process.NetworkUtils;
-import org.sonar.process.NetworkUtilsImpl;
+import static com.google.common.collect.ImmutableList.of;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.spy;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
import static org.sonar.process.ProcessId.ELASTICSEARCH;
@@ -43,23 +54,18 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
import static org.sonar.process.ProcessProperties.Property.SEARCH_HOST;
+@RunWith(DataProviderRunner.class)
public class ClusterSettingsTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private InetAddress nonLoopbackLocal = InetAddress.getLoopbackAddress();
- private NetworkUtils network = spy(NetworkUtilsImpl.INSTANCE);
-
- @Before
- public void setUp() throws Exception {
- when(network.isLocalInetAddress(nonLoopbackLocal)).thenReturn(true);
- when(network.isLoopbackInetAddress(nonLoopbackLocal)).thenReturn(false);
- }
+ private NetworkUtils network = Mockito.mock(NetworkUtils.class);
@Test
- public void test_isClusterEnabled() {
- TestAppSettings settings = newSettingsForAppNode().set(CLUSTER_ENABLED.getKey(), "true");
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void test_isClusterEnabled(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host).set(CLUSTER_ENABLED.getKey(), "true");
assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue();
settings = new TestAppSettings().set(CLUSTER_ENABLED.getKey(), "false");
@@ -78,11 +84,12 @@ public class ClusterSettingsTest {
}
@Test
- public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void getEnabledProcesses_returns_configured_processes_in_cluster_mode(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, WEB_SERVER);
- settings = newSettingsForSearchNode();
+ settings = newSettingsForSearchNode(host);
assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(ELASTICSEARCH);
}
@@ -110,8 +117,9 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_throws_MessageException_if_internal_property_for_startup_leader_is_configured() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_internal_property_for_startup_leader_is_configured(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.set("sonar.cluster.web.startupLeader", "true");
expectedException.expect(MessageException.class);
@@ -131,8 +139,9 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_throws_MessageException_if_h2_on_application_node() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_h2_on_application_node(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.set("sonar.jdbc.url", "jdbc:h2:mem");
expectedException.expect(MessageException.class);
@@ -142,8 +151,11 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_does_not_verify_h2_on_search_node() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_does_not_verify_h2_on_search_node(String host) {
+ mockValidHost(host);
+ mockLocalNonLoopback(host);
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set("sonar.jdbc.url", "jdbc:h2:mem");
// do not fail
@@ -151,8 +163,9 @@ public class ClusterSettingsTest {
}
@Test
- public void accept_throws_MessageException_on_application_node_if_default_jdbc_url() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_on_application_node_if_default_jdbc_url(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.clearProperty(JDBC_URL.getKey());
expectedException.expect(MessageException.class);
@@ -168,82 +181,97 @@ public class ClusterSettingsTest {
}
@Test
- public void isLocalElasticsearchEnabled_returns_true_on_search_node() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void isLocalElasticsearchEnabled_returns_true_on_search_node(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
}
@Test
- public void shouldStartHazelcast_must_be_true_on_AppNode() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_must_be_true_on_AppNode(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isTrue();
}
@Test
- public void shouldStartHazelcast_must_be_false_on_SearchNode() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_must_be_false_on_SearchNode(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
}
@Test
- public void shouldStartHazelcast_must_be_false_when_cluster_not_activated() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_must_be_false_when_cluster_not_activated(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set(CLUSTER_ENABLED.getKey(), "false");
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
- settings = newSettingsForAppNode();
+ settings = newSettingsForAppNode(host);
settings.set(CLUSTER_ENABLED.getKey(), "false");
assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
}
@Test
- public void isLocalElasticsearchEnabled_returns_true_for_a_application_node() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void isLocalElasticsearchEnabled_returns_true_for_a_application_node(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse();
}
@Test
- public void accept_throws_MessageException_if_searchHost_is_missing() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_searchHost_is_missing(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.clearProperty(SEARCH_HOST.getKey());
assertThatPropertyIsMandatory(settings, SEARCH_HOST.getKey());
}
@Test
- public void accept_throws_MessageException_if_searchHost_is_empty() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_searchHost_is_empty(String host) {
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set(SEARCH_HOST.getKey(), "");
assertThatPropertyIsMandatory(settings, SEARCH_HOST.getKey());
}
@Test
- public void accept_throws_MessageException_on_app_node_if_clusterHosts_is_missing() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_on_app_node_if_clusterHosts_is_missing(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.clearProperty(CLUSTER_HZ_HOSTS.getKey());
assertThatPropertyIsMandatory(settings, CLUSTER_HZ_HOSTS.getKey());
}
@Test
- public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_clusterSearchHosts_is_missing(String host) {
+ mockValidHost(host);
+ mockLocalNonLoopback(host);
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.clearProperty(CLUSTER_SEARCH_HOSTS.getKey());
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS.getKey());
}
@Test
- public void accept_throws_MessageException_if_clusterSearchHosts_is_empty() {
- TestAppSettings settings = newSettingsForSearchNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_clusterSearchHosts_is_empty(String host) {
+ mockValidHost(host);
+ mockLocalNonLoopback(host);
+ TestAppSettings settings = newSettingsForSearchNode(host);
settings.set(CLUSTER_SEARCH_HOSTS.getKey(), "");
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS.getKey());
}
@Test
- public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes() {
- TestAppSettings settings = newSettingsForAppNode();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes(String host) {
+ TestAppSettings settings = newSettingsForAppNode(host);
settings.clearProperty("sonar.auth.jwtBase64Hs256Secret");
assertThatPropertyIsMandatory(settings, "sonar.auth.jwtBase64Hs256Secret");
}
@@ -255,14 +283,205 @@ public class ClusterSettingsTest {
}
@Test
- public void shouldStartHazelcast_should_return_false_on_SearchNode() {
- assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForSearchNode())).isFalse();
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_should_return_false_on_SearchNode(String host) {
+ assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForSearchNode(host))).isFalse();
}
+ @Test
+ @UseDataProvider("validIPv4andIPv6Addresses")
+ public void shouldStartHazelcast_should_return_true_on_AppNode(String host) {
+ assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForAppNode(host))).isTrue();
+ }
@Test
- public void shouldStartHazelcast_should_return_true_on_AppNode() {
- assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForAppNode())).isTrue();
+ public void validate_host_in_any_cluster_property_of_APP_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "application")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_HZ_HOSTS.getKey(), "remote_hz_host_1,remote_hz_host_2")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar")
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde");
+
+ verifyHostIsChecked(settings, of("hz_host"), "Address in property sonar.cluster.node.host is not a valid address: hz_host");
+ verifyHostIsChecked(settings, of("remote_hz_host_1"), "Address in property sonar.cluster.hosts is not a valid address: remote_hz_host_1");
+ verifyHostIsChecked(settings, of("remote_hz_host_2"), "Address in property sonar.cluster.hosts is not a valid address: remote_hz_host_2");
+ verifyHostIsChecked(settings,
+ of("remote_hz_host_1", "remote_hz_host_2"),
+ "Address in property sonar.cluster.hosts is not a valid address: remote_hz_host_1, remote_hz_host_2");
+ verifyHostIsChecked(settings, of("remote_search_host_1"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_1");
+ verifyHostIsChecked(settings, of("remote_search_host_2"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_2");
+ verifyHostIsChecked(settings,
+ of("remote_search_host_1", "remote_search_host_2"),
+ "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_1, remote_search_host_2");
+ }
+
+ @Test
+ public void validate_host_resolved_in_any_cluster_property_of_SEARCH_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "search")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(SEARCH_HOST.getKey(), "search_host");
+
+ verifyHostIsChecked(settings, of("hz_host"), "Address in property sonar.cluster.node.host is not a valid address: hz_host");
+ verifyHostIsChecked(settings, of("remote_search_host_1"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_1");
+ verifyHostIsChecked(settings, of("remote_search_host_2"), "Address in property sonar.cluster.search.hosts is not a valid address: remote_search_host_2");
+ verifyHostIsChecked(settings, of("search_host"), "Address in property sonar.search.host is not a valid address: search_host");
+ }
+
+ private void verifyHostIsChecked(TestAppSettings settings, Collection<String> invalidHosts, String expectedMessage) {
+ reset(network);
+ mockAllHostsValidBut(invalidHosts);
+ mockLocalNonLoopback("hz_host", "search_host");
+ assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps()))
+ .isInstanceOf(MessageException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ @Test
+ public void ensure_no_loopback_host_in_properties_of_APP_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "application")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_HZ_HOSTS.getKey(), "remote_hz_host_1,remote_hz_host_2")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar")
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde");
+
+ verifyLoopbackChecked(settings, of("hz_host"), "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ verifyLoopbackChecked(settings, of("remote_search_host_1"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1");
+ verifyLoopbackChecked(settings, of("remote_search_host_2"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_2");
+ verifyLoopbackChecked(settings,
+ of("remote_search_host_1", "remote_search_host_2"),
+ "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1, remote_search_host_2");
+ verifyLoopbackChecked(settings, of("remote_hz_host_1"), "Property sonar.cluster.hosts must not contain a loopback address: remote_hz_host_1");
+ verifyLoopbackChecked(settings, of("remote_hz_host_2"), "Property sonar.cluster.hosts must not contain a loopback address: remote_hz_host_2");
+ verifyLoopbackChecked(settings,
+ of("remote_hz_host_1", "remote_hz_host_2"),
+ "Property sonar.cluster.hosts must not contain a loopback address: remote_hz_host_1, remote_hz_host_2");
+ }
+
+ @Test
+ public void ensure_no_loopback_host_in_properties_of_SEARCH_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "search")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(SEARCH_HOST.getKey(), "search_host");
+
+ verifyLoopbackChecked(settings, of("hz_host"), "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ verifyLoopbackChecked(settings, of("search_host"), "Property sonar.search.host must be a local non-loopback address: search_host");
+ verifyLoopbackChecked(settings, of("remote_search_host_1"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1");
+ verifyLoopbackChecked(settings, of("remote_search_host_2"), "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_2");
+ verifyLoopbackChecked(settings,
+ of("remote_search_host_1", "remote_search_host_2"),
+ "Property sonar.cluster.search.hosts must not contain a loopback address: remote_search_host_1, remote_search_host_2");
+ }
+
+ private void verifyLoopbackChecked(TestAppSettings settings, Collection<String> hosts, String expectedMessage) {
+ reset(network);
+ mockAllHostsValid();
+ mockLocalNonLoopback("hz_host", "search_host");
+ // will overwrite above move if necessary
+ hosts.forEach(this::mockLoopback);
+ assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps()))
+ .isInstanceOf(MessageException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ @Test
+ public void ensure_HZ_HOST_is_local_non_loopback_in_properties_of_APP_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "application")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_HZ_HOSTS.getKey(), "remote_hz_host_1,remote_hz_host_2")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar")
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde");
+
+ verifyLocalChecked(settings, "hz_host", "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ }
+
+ @Test
+ public void ensure_HZ_HOST_and_SEARCH_HOST_are_local_non_loopback_in_properties_of_SEARCH_node() {
+ TestAppSettings settings = new TestAppSettings()
+ .set(CLUSTER_ENABLED.getKey(), "true")
+ .set(CLUSTER_NODE_TYPE.getKey(), "search")
+ .set(CLUSTER_NODE_HOST.getKey(), "hz_host")
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001")
+ .set(SEARCH_HOST.getKey(), "search_host");
+
+ verifyLocalChecked(settings, "hz_host", "Property sonar.cluster.node.host must be a local non-loopback address: hz_host");
+ verifyLocalChecked(settings, "search_host", "Property sonar.search.host must be a local non-loopback address: search_host");
+ }
+
+ private void verifyLocalChecked(TestAppSettings settings, String host, String expectedMessage) {
+ reset(network);
+ mockAllHostsValid();
+ mockLocalNonLoopback("hz_host", "search_host");
+ // will overwrite above move if necessary
+ mockAllNonLoopback();
+ mockNonLocal(host);
+ assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps()))
+ .isInstanceOf(MessageException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ private void mockAllNonLoopback() {
+ when(network.isLoopback(anyString())).thenReturn(false);
+ }
+
+ private void mockNonLocal(String search_host) {
+ when(network.isLocal(search_host)).thenReturn(false);
+ }
+
+ private void mockLoopback(String host) {
+ when(network.isLoopback(host)).thenReturn(true);
+ }
+
+ private void mockValidHost(String host) {
+ String unbracketedHost = host.startsWith("[") ? host.substring(1, host.length() - 1) : host;
+ when(network.toInetAddress(unbracketedHost)).thenReturn(Optional.of(InetAddress.getLoopbackAddress()));
+ }
+
+ public void mockAllHostsValid() {
+ when(network.toInetAddress(anyString())).thenReturn(Optional.of(InetAddress.getLoopbackAddress()));
+ }
+
+ public void mockAllHostsValidBut(Collection<String> hosts) {
+ when(network.toInetAddress(anyString()))
+ .thenAnswer((Answer<Optional<InetAddress>>) invocation -> {
+ Object arg = invocation.getArgument(0);
+ if (hosts.contains(arg)) {
+ return Optional.empty();
+ }
+ return Optional.of(InetAddress.getLoopbackAddress());
+ });
+ }
+
+ private void mockLocalNonLoopback(String host, String... otherhosts) {
+ Stream.concat(Stream.of(host), Arrays.stream(otherhosts))
+ .forEach(h -> {
+ String unbracketedHost = h.startsWith("[") ? h.substring(1, h.length() - 1) : h;
+ when(network.isLocal(unbracketedHost)).thenReturn(true);
+ when(network.isLoopback(unbracketedHost)).thenReturn(false);
+ });
+
+ }
+
+ @DataProvider
+ public static Object[][] validIPv4andIPv6Addresses() {
+ return new Object[][] {
+ {"10.150.0.188"},
+ {"[fe80::fde2:607e:ae56:e636]"},
+ };
}
private void assertThatPropertyIsMandatory(TestAppSettings settings, String key) {
@@ -272,24 +491,25 @@ public class ClusterSettingsTest {
new ClusterSettings(network).accept(settings.getProps());
}
- private TestAppSettings newSettingsForAppNode() {
+ private TestAppSettings newSettingsForAppNode(String host) {
return new TestAppSettings()
.set(CLUSTER_ENABLED.getKey(), "true")
.set(CLUSTER_NODE_TYPE.getKey(), "application")
- .set(CLUSTER_NODE_HOST.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_HZ_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_SEARCH_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_NODE_HOST.getKey(), host)
+ .set(CLUSTER_HZ_HOSTS.getKey(), host)
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), host + ":9001")
.set("sonar.auth.jwtBase64Hs256Secret", "abcde")
.set(JDBC_URL.getKey(), "jdbc:postgresql://localhost/sonar");
}
- private TestAppSettings newSettingsForSearchNode() {
+ private TestAppSettings newSettingsForSearchNode(String host) {
return new TestAppSettings()
.set(CLUSTER_ENABLED.getKey(), "true")
.set(CLUSTER_NODE_TYPE.getKey(), "search")
- .set(CLUSTER_NODE_HOST.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_HZ_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
- .set(CLUSTER_SEARCH_HOSTS.getKey(), nonLoopbackLocal.getHostAddress())
- .set(SEARCH_HOST.getKey(), nonLoopbackLocal.getHostAddress());
+ .set(CLUSTER_NODE_HOST.getKey(), host)
+ .set(CLUSTER_HZ_HOSTS.getKey(), host)
+ .set(CLUSTER_SEARCH_HOSTS.getKey(), host + ":9001")
+ .set(SEARCH_HOST.getKey(), host);
}
+
}