@@ -19,7 +19,6 @@ | |||
*/ | |||
package org.sonar.application; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.net.HostAndPort; | |||
import java.util.Arrays; | |||
import java.util.Set; | |||
@@ -32,7 +31,6 @@ import org.sonar.application.es.EsConnector; | |||
import org.sonar.application.es.EsConnectorImpl; | |||
import org.sonar.process.ProcessId; | |||
import org.sonar.process.Props; | |||
import org.sonar.process.cluster.NodeType; | |||
import org.sonar.process.cluster.hz.HazelcastMember; | |||
import org.sonar.process.cluster.hz.HazelcastMemberBuilder; | |||
@@ -42,8 +40,6 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HZ_PORT; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_NAME; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.SEARCH_PORT; | |||
public class AppStateFactory { | |||
private final AppSettings settings; | |||
@@ -73,13 +69,6 @@ public class AppStateFactory { | |||
} | |||
private static EsConnector createEsConnector(Props props) { | |||
NodeType nodeType = ClusterSettings.toNodeType(props); | |||
if (nodeType == NodeType.SEARCH) { | |||
String host = props.nonNullValue(SEARCH_HOST.getKey()); | |||
String port = props.nonNullValue(SEARCH_PORT.getKey()); | |||
return new EsConnectorImpl(ImmutableSet.of(HostAndPort.fromParts(host, Integer.valueOf(port)))); | |||
} | |||
String searchHosts = props.nonNullValue(CLUSTER_SEARCH_HOSTS.getKey()); | |||
Set<HostAndPort> hostAndPorts = Arrays.stream(searchHosts.split(",")) | |||
.map(HostAndPort::fromString) |
@@ -45,14 +45,15 @@ 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_ES_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_HZ_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_TYPE; | |||
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> { | |||
@@ -75,11 +76,6 @@ 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: | |||
@@ -87,24 +83,37 @@ public class ClusterSettings implements Consumer<Props> { | |||
requireValue(props, AUTH_JWT_SECRET); | |||
Set<AddressAndPort> hzNodes = parseHosts(CLUSTER_HZ_HOSTS, requireValue(props, CLUSTER_HZ_HOSTS)); | |||
ensureNotLoopbackAddresses(CLUSTER_HZ_HOSTS, hzNodes); | |||
checkClusterNodeHost(props); | |||
checkClusterSearchHosts(props); | |||
break; | |||
case SEARCH: | |||
AddressAndPort searchHost = parseAndCheckHost(SEARCH_HOST, requireValue(props, SEARCH_HOST)); | |||
ensureLocalButNotLoopbackAddress(SEARCH_HOST, searchHost); | |||
requireValue(props, SEARCH_PORT); | |||
AddressAndPort searchHost = parseAndCheckHost(CLUSTER_NODE_SEARCH_HOST, requireValue(props, CLUSTER_NODE_SEARCH_HOST)); | |||
ensureLocalButNotLoopbackAddress(CLUSTER_NODE_SEARCH_HOST, searchHost); | |||
AddressAndPort esHost = parseAndCheckHost(CLUSTER_NODE_ES_HOST, requireValue(props, CLUSTER_NODE_ES_HOST)); | |||
ensureLocalButNotLoopbackAddress(CLUSTER_NODE_ES_HOST, esHost); | |||
checkClusterEsHosts(props); | |||
break; | |||
default: | |||
throw new UnsupportedOperationException("Unknown value: " + nodeType); | |||
} | |||
} | |||
private void checkCommonProperties(Props props) { | |||
private void checkClusterNodeHost(Props props) { | |||
AddressAndPort clusterNodeHost = parseAndCheckHost(CLUSTER_NODE_HOST, requireValue(props, CLUSTER_NODE_HOST)); | |||
ensureLocalButNotLoopbackAddress(CLUSTER_NODE_HOST, clusterNodeHost); | |||
} | |||
private void checkClusterSearchHosts(Props props) { | |||
Set<AddressAndPort> searchHosts = parseHosts(CLUSTER_SEARCH_HOSTS, requireValue(props, CLUSTER_SEARCH_HOSTS)); | |||
ensureNotLoopbackAddresses(CLUSTER_SEARCH_HOSTS, searchHosts); | |||
} | |||
private void checkClusterEsHosts(Props props) { | |||
Set<AddressAndPort> esHosts = parseHosts(CLUSTER_ES_HOSTS, requireValue(props, CLUSTER_ES_HOSTS)); | |||
ensureNotLoopbackAddresses(CLUSTER_ES_HOSTS, esHosts); | |||
ensureEitherPortsAreProvidedOrOnlyHosts(CLUSTER_ES_HOSTS, esHosts); | |||
} | |||
private Set<AddressAndPort> parseHosts(Property property, String value) { | |||
Set<AddressAndPort> res = stream(value.split(",")) | |||
.filter(Objects::nonNull) | |||
@@ -182,6 +191,15 @@ public class ClusterSettings implements Consumer<Props> { | |||
} | |||
} | |||
private static void ensureEitherPortsAreProvidedOrOnlyHosts(Property property, Set<AddressAndPort> addressAndPorts) { | |||
Set<AddressAndPort> hostsWithoutPort = addressAndPorts.stream() | |||
.filter(t -> !t.hasPort()) | |||
.collect(toSet()); | |||
if (!hostsWithoutPort.isEmpty() && hostsWithoutPort.size() != addressAndPorts.size()) { | |||
throw new MessageException(format("Entries in property %s must not mix 'host:port' and 'host'. Provide hosts without port only or hosts with port only.", property.getKey())); | |||
} | |||
} | |||
private static class AddressAndPort { | |||
private static final int NO_PORT = -1; | |||
@@ -24,23 +24,33 @@ import java.net.UnknownHostException; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import javax.annotation.Nullable; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.MessageException; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import org.sonar.process.System2; | |||
import static java.lang.String.valueOf; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ES_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NAME; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_PORT; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_NAME; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_PORT; | |||
import static org.sonar.process.ProcessProperties.Property.SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.SEARCH_INITIAL_STATE_TIMEOUT; | |||
import static org.sonar.process.ProcessProperties.Property.SEARCH_PORT; | |||
import static org.sonar.process.ProcessProperties.Property.SEARCH_TRANSPORT_PORT; | |||
public class EsSettings { | |||
private static final String ES_HTTP_HOST_KEY = "http.host"; | |||
private static final String ES_HTTP_PORT_KEY = "http.port"; | |||
private static final String ES_TRANSPORT_HOST_KEY = "transport.host"; | |||
private static final String ES_TRANSPORT_PORT_KEY = "transport.port"; | |||
private static final Logger LOGGER = LoggerFactory.getLogger(EsSettings.class); | |||
private static final String STANDALONE_NODE_NAME = "sonarqube"; | |||
@@ -53,6 +63,7 @@ public class EsSettings { | |||
private final boolean clusterEnabled; | |||
private final String clusterName; | |||
private final String nodeName; | |||
private final InetAddress loopbackAddress; | |||
public EsSettings(Props props, EsInstallation fileSystem, System2 system2) { | |||
this.props = props; | |||
@@ -65,6 +76,7 @@ public class EsSettings { | |||
} else { | |||
this.nodeName = STANDALONE_NODE_NAME; | |||
} | |||
this.loopbackAddress = InetAddress.getLoopbackAddress(); | |||
String esJvmOptions = system2.getenv("ES_JVM_OPTIONS"); | |||
if (esJvmOptions != null && !esJvmOptions.trim().isEmpty()) { | |||
LOGGER.warn("ES_JVM_OPTIONS is defined but will be ignored. " + | |||
@@ -75,9 +87,10 @@ public class EsSettings { | |||
public Map<String, String> build() { | |||
Map<String, String> builder = new HashMap<>(); | |||
configureFileSystem(builder); | |||
InetAddress host = configureNetwork(builder); | |||
configureCluster(builder, host); | |||
configureNetwork(builder); | |||
configureCluster(builder); | |||
configureOthers(builder); | |||
LOGGER.info("Elasticsearch listening on {}:{}", builder.get(ES_HTTP_HOST_KEY), builder.get(ES_HTTP_PORT_KEY)); | |||
return builder; | |||
} | |||
@@ -86,39 +99,49 @@ public class EsSettings { | |||
builder.put("path.logs", fileSystem.getLogDirectory().getAbsolutePath()); | |||
} | |||
private InetAddress configureNetwork(Map<String, String> builder) { | |||
InetAddress host = readHost(); | |||
int httpPort = Integer.parseInt(props.nonNullValue(SEARCH_PORT.getKey())); | |||
LOGGER.info("Elasticsearch listening on {}:{}", host, httpPort); | |||
private void configureNetwork(Map<String, String> builder) { | |||
InetAddress searchHost = resolveAddress(SEARCH_HOST); | |||
int searchPort = Integer.parseInt(props.nonNullValue(SEARCH_PORT.getKey())); | |||
// see https://github.com/lmenezes/elasticsearch-kopf/issues/195 | |||
builder.put("http.cors.enabled", valueOf(true)); | |||
builder.put("http.cors.allow-origin", "*"); | |||
builder.put("http.host", host.getHostAddress()); | |||
builder.put("http.port", valueOf(httpPort)); | |||
builder.put(ES_HTTP_HOST_KEY, searchHost.getHostAddress()); | |||
builder.put(ES_HTTP_PORT_KEY, valueOf(searchPort)); | |||
builder.put("network.host", valueOf(host.getHostAddress())); | |||
builder.put("network.host", valueOf(searchHost.getHostAddress())); | |||
// Elasticsearch sets the default value of TCP reuse address to true only on non-MSWindows machines, but why ? | |||
builder.put("network.tcp.reuse_address", valueOf(true)); | |||
// FIXME remove definition of transport properties when Web and CE have moved to ES Rest client | |||
int tcpPort = props.valueAsInt(SEARCH_TRANSPORT_PORT.getKey(), 9002); | |||
builder.put("transport.port", valueOf(tcpPort)); | |||
builder.put("transport.host", valueOf(host.getHostAddress())); | |||
int transportPort = props.valueAsInt(SEARCH_TRANSPORT_PORT.getKey(), 9002); | |||
builder.put(ES_TRANSPORT_HOST_KEY, searchHost.getHostAddress()); | |||
builder.put(ES_TRANSPORT_PORT_KEY, valueOf(transportPort)); | |||
} | |||
return host; | |||
private InetAddress resolveAddress(ProcessProperties.Property prop) { | |||
return resolveAddress(prop, null); | |||
} | |||
private InetAddress readHost() { | |||
String hostProperty = props.nonNullValue(SEARCH_HOST.getKey()); | |||
private InetAddress resolveAddress(ProcessProperties.Property prop, @Nullable InetAddress defaultAddress) { | |||
String address; | |||
if (defaultAddress == null) { | |||
address = props.nonNullValue(prop.getKey()); | |||
} else { | |||
address = props.value(prop.getKey()); | |||
if (address == null) { | |||
return defaultAddress; | |||
} | |||
} | |||
try { | |||
return InetAddress.getByName(hostProperty); | |||
return InetAddress.getByName(address); | |||
} catch (UnknownHostException e) { | |||
throw new IllegalStateException("Can not resolve host [" + hostProperty + "]. Please check network settings and property " + SEARCH_HOST.getKey(), e); | |||
throw new IllegalStateException("Can not resolve host [" + address + "]. Please check network settings and property " + prop.getKey(), e); | |||
} | |||
} | |||
private void configureCluster(Map<String, String> builder, InetAddress host) { | |||
private void configureCluster(Map<String, String> builder) { | |||
// Default value in a standalone mode, not overridable | |||
String initialStateTimeOut = "30s"; | |||
@@ -126,11 +149,18 @@ public class EsSettings { | |||
if (clusterEnabled) { | |||
initialStateTimeOut = props.value(SEARCH_INITIAL_STATE_TIMEOUT.getKey(), "120s"); | |||
int tcpPort = props.valueAsInt(SEARCH_TRANSPORT_PORT.getKey(), 9002); | |||
builder.put("transport.port", valueOf(tcpPort)); | |||
builder.put("transport.host", valueOf(host.getHostAddress())); | |||
String nodeSearchHost = resolveAddress(CLUSTER_NODE_SEARCH_HOST, loopbackAddress).getHostAddress(); | |||
int nodeSearchPort = props.valueAsInt(CLUSTER_NODE_SEARCH_PORT.getKey(), 9001); | |||
builder.put(ES_HTTP_HOST_KEY, nodeSearchHost); | |||
builder.put(ES_HTTP_PORT_KEY, valueOf(nodeSearchPort)); | |||
String nodeTransportHost = resolveAddress(CLUSTER_NODE_ES_HOST, loopbackAddress).getHostAddress(); | |||
int nodeTransportPort = props.valueAsInt(CLUSTER_NODE_ES_PORT.getKey(), 9002); | |||
builder.put(ES_TRANSPORT_HOST_KEY, nodeTransportHost); | |||
builder.put(ES_TRANSPORT_PORT_KEY, valueOf(nodeTransportPort)); | |||
builder.put("network.host", nodeTransportHost); | |||
String hosts = props.value(CLUSTER_SEARCH_HOSTS.getKey(), ""); | |||
String hosts = props.value(CLUSTER_ES_HOSTS.getKey(), loopbackAddress.getHostAddress()); | |||
LOGGER.info("Elasticsearch cluster enabled. Connect to hosts [{}]", hosts); | |||
builder.put("discovery.seed_hosts", hosts); | |||
builder.put("cluster.initial_master_nodes", hosts); |
@@ -23,31 +23,31 @@ import java.net.InetAddress; | |||
import java.util.Optional; | |||
import org.hamcrest.CoreMatchers; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.process.MessageException; | |||
import org.sonar.process.NetworkUtils; | |||
import org.sonar.process.NetworkUtilsImpl; | |||
import org.sonar.process.Props; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.junit.Assume.assumeThat; | |||
import static org.mockito.Mockito.spy; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ES_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_HZ_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_TYPE; | |||
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; | |||
public class ClusterSettingsLoopbackTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private final InetAddress loopback = InetAddress.getLoopbackAddress(); | |||
private final NetworkUtils network = spy(NetworkUtilsImpl.INSTANCE); | |||
private InetAddress loopback = InetAddress.getLoopbackAddress(); | |||
private InetAddress nonLoopbackLocal; | |||
private NetworkUtils network = spy(NetworkUtilsImpl.INSTANCE); | |||
@Before | |||
public void setUp() { | |||
@@ -58,37 +58,39 @@ public class ClusterSettingsLoopbackTest { | |||
} | |||
@Test | |||
public void ClusterSettings_throws_MessageException_if_host_of_search_node_is_loopback() { | |||
verifySearchFailureIfLoopback(CLUSTER_NODE_HOST.getKey()); | |||
verifySearchFailureIfLoopback(CLUSTER_SEARCH_HOSTS.getKey()); | |||
verifySearchFailureIfLoopback(CLUSTER_HZ_HOSTS.getKey()); | |||
verifySearchFailureIfLoopback(SEARCH_HOST.getKey()); | |||
} | |||
public void ClusterSettings_throws_MessageException_if_es_http_host_of_search_node_is_loopback() { | |||
TestAppSettings settings = newSettingsForSearchNode(); | |||
settings.set(CLUSTER_NODE_SEARCH_HOST.getKey(), loopback.getHostAddress()); | |||
Props props = settings.getProps(); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
@Test | |||
public void ClusterSettings_throws_MessageException_if_host_of_app_node_is_loopback() { | |||
verifyAppFailureIfLoopback(CLUSTER_NODE_HOST.getKey()); | |||
verifyAppFailureIfLoopback(CLUSTER_SEARCH_HOSTS.getKey()); | |||
verifyAppFailureIfLoopback(CLUSTER_HZ_HOSTS.getKey()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Property " + CLUSTER_NODE_SEARCH_HOST.getKey() + " must be a local non-loopback address: " + loopback.getHostAddress()); | |||
} | |||
private void verifySearchFailureIfLoopback(String propertyKey) { | |||
@Test | |||
public void ClusterSettings_throws_MessageException_if_es_transport_host_of_search_node_is_loopback() { | |||
TestAppSettings settings = newSettingsForSearchNode(); | |||
verifyFailure(propertyKey, settings); | |||
} | |||
settings.set(CLUSTER_NODE_ES_HOST.getKey(), loopback.getHostAddress()); | |||
Props props = settings.getProps(); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
private void verifyAppFailureIfLoopback(String propertyKey) { | |||
TestAppSettings settings = newSettingsForAppNode(); | |||
verifyFailure(propertyKey, settings); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Property " + CLUSTER_NODE_ES_HOST.getKey() + " must be a local non-loopback address: " + loopback.getHostAddress()); | |||
} | |||
private void verifyFailure(String propertyKey, TestAppSettings settings) { | |||
settings.set(propertyKey, loopback.getHostAddress()); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Property " + propertyKey + " must be a local non-loopback address: " + loopback.getHostAddress()); | |||
@Test | |||
public void ClusterSettings_throws_MessageException_if_host_of_app_node_is_loopback() { | |||
TestAppSettings settings = newSettingsForAppNode(); | |||
settings.set(CLUSTER_NODE_HOST.getKey(), loopback.getHostAddress()); | |||
Props props = settings.getProps(); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Property " + CLUSTER_NODE_HOST.getKey() + " must be a local non-loopback address: " + loopback.getHostAddress()); | |||
} | |||
private TestAppSettings newSettingsForAppNode() { | |||
@@ -106,9 +108,8 @@ public class ClusterSettingsLoopbackTest { | |||
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_ES_HOSTS.getKey(), nonLoopbackLocal.getHostAddress()) | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), nonLoopbackLocal.getHostAddress()) | |||
.set(CLUSTER_NODE_ES_HOST.getKey(), nonLoopbackLocal.getHostAddress()); | |||
} | |||
} |
@@ -27,18 +27,18 @@ 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.Props; | |||
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.assertj.core.api.Assertions.assertThatCode; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.mockito.ArgumentMatchers.anyString; | |||
import static org.mockito.Mockito.reset; | |||
@@ -47,20 +47,19 @@ import static org.sonar.process.ProcessId.COMPUTE_ENGINE; | |||
import static org.sonar.process.ProcessId.ELASTICSEARCH; | |||
import static org.sonar.process.ProcessId.WEB_SERVER; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ES_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_HZ_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_TYPE; | |||
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 NetworkUtils network = Mockito.mock(NetworkUtils.class); | |||
private final NetworkUtils network = Mockito.mock(NetworkUtils.class); | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
@@ -97,11 +96,12 @@ public class ClusterSettingsTest { | |||
public void accept_throws_MessageException_if_no_node_type_is_configured() { | |||
TestAppSettings settings = new TestAppSettings(); | |||
settings.set(CLUSTER_ENABLED.getKey(), "true"); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Property sonar.cluster.node.type is mandatory"); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Property sonar.cluster.node.type is mandatory"); | |||
} | |||
@Test | |||
@@ -109,11 +109,12 @@ public class ClusterSettingsTest { | |||
TestAppSettings settings = new TestAppSettings(); | |||
settings.set(CLUSTER_ENABLED.getKey(), "true"); | |||
settings.set(CLUSTER_NODE_TYPE.getKey(), "bla"); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Invalid value for property sonar.cluster.node.type: [bla], only [application, search] are allowed"); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Invalid value for property sonar.cluster.node.type: [bla], only [application, search] are allowed"); | |||
} | |||
@Test | |||
@@ -121,11 +122,12 @@ public class ClusterSettingsTest { | |||
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"); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Property [sonar.cluster.web.startupLeader] is forbidden"); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Property [sonar.cluster.web.startupLeader] is forbidden"); | |||
} | |||
@Test | |||
@@ -143,11 +145,12 @@ public class ClusterSettingsTest { | |||
public void accept_throws_MessageException_if_h2_on_application_node(String host) { | |||
TestAppSettings settings = newSettingsForAppNode(host); | |||
settings.set("sonar.jdbc.url", "jdbc:h2:mem"); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Embedded database is not supported in cluster mode"); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Embedded database is not supported in cluster mode"); | |||
} | |||
@Test | |||
@@ -167,11 +170,12 @@ public class ClusterSettingsTest { | |||
public void accept_throws_MessageException_on_application_node_if_default_jdbc_url(String host) { | |||
TestAppSettings settings = newSettingsForAppNode(host); | |||
settings.clearProperty(JDBC_URL.getKey()); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Embedded database is not supported in cluster mode"); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Embedded database is not supported in cluster mode"); | |||
} | |||
@Test | |||
@@ -226,18 +230,54 @@ public class ClusterSettingsTest { | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
public void accept_throws_MessageException_if_searchHost_is_missing(String host) { | |||
public void accept_throws_MessageException_if_clusterNodeEsHost_is_missing(String host) { | |||
TestAppSettings settings = newSettingsForSearchNode(host); | |||
settings.clearProperty(SEARCH_HOST.getKey()); | |||
assertThatPropertyIsMandatory(settings, SEARCH_HOST.getKey()); | |||
String searchHost = "search_host"; | |||
mockValidHost(searchHost); | |||
mockLocalNonLoopback(searchHost); | |||
settings.set(CLUSTER_NODE_SEARCH_HOST.getKey(), searchHost); | |||
settings.clearProperty(CLUSTER_NODE_ES_HOST.getKey()); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_NODE_ES_HOST.getKey()); | |||
} | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
public void accept_throws_MessageException_if_searchHost_is_empty(String host) { | |||
public void accept_throws_MessageException_if_clusterNodeSearchHost_is_missing(String host) { | |||
TestAppSettings settings = newSettingsForSearchNode(host); | |||
settings.set(SEARCH_HOST.getKey(), ""); | |||
assertThatPropertyIsMandatory(settings, SEARCH_HOST.getKey()); | |||
String esHost = "es_host"; | |||
mockValidHost(esHost); | |||
mockLocalNonLoopback(esHost); | |||
settings.set(CLUSTER_NODE_ES_HOST.getKey(), esHost); | |||
settings.clearProperty(CLUSTER_NODE_SEARCH_HOST.getKey()); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_NODE_SEARCH_HOST.getKey()); | |||
} | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
public void accept_throws_MessageException_if_sonarClusterNodeSearchHost_is_empty(String host) { | |||
TestAppSettings settings = newSettingsForSearchNode(host); | |||
settings.set(CLUSTER_NODE_SEARCH_HOST.getKey(), ""); | |||
String esHost = "es_host"; | |||
mockValidHost(esHost); | |||
mockLocalNonLoopback(esHost); | |||
settings.set(CLUSTER_NODE_ES_HOST.getKey(), esHost); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_NODE_SEARCH_HOST.getKey()); | |||
} | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
public void accept_throws_MessageException_if_sonarClusterNodeEsHost_is_empty(String host) { | |||
TestAppSettings settings = newSettingsForSearchNode(host); | |||
settings.set(CLUSTER_NODE_ES_HOST.getKey(), ""); | |||
String searchHost = "search_host"; | |||
mockValidHost(searchHost); | |||
mockLocalNonLoopback(searchHost); | |||
settings.set(CLUSTER_NODE_SEARCH_HOST.getKey(), searchHost); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_NODE_ES_HOST.getKey()); | |||
} | |||
@Test | |||
@@ -250,22 +290,22 @@ public class ClusterSettingsTest { | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
public void accept_throws_MessageException_if_clusterSearchHosts_is_missing(String host) { | |||
public void accept_throws_MessageException_if_clusterEsHosts_is_missing(String host) { | |||
mockValidHost(host); | |||
mockLocalNonLoopback(host); | |||
TestAppSettings settings = newSettingsForSearchNode(host); | |||
settings.clearProperty(CLUSTER_SEARCH_HOSTS.getKey()); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS.getKey()); | |||
settings.clearProperty(CLUSTER_ES_HOSTS.getKey()); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_ES_HOSTS.getKey()); | |||
} | |||
@Test | |||
@UseDataProvider("validIPv4andIPv6Addresses") | |||
public void accept_throws_MessageException_if_clusterSearchHosts_is_empty(String host) { | |||
public void accept_throws_MessageException_if_clusterEsHosts_is_empty(String host) { | |||
mockValidHost(host); | |||
mockLocalNonLoopback(host); | |||
TestAppSettings settings = newSettingsForSearchNode(host); | |||
settings.set(CLUSTER_SEARCH_HOSTS.getKey(), ""); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS.getKey()); | |||
settings.set(CLUSTER_ES_HOSTS.getKey(), ""); | |||
assertThatPropertyIsMandatory(settings, CLUSTER_ES_HOSTS.getKey()); | |||
} | |||
@Test | |||
@@ -323,21 +363,22 @@ public class ClusterSettingsTest { | |||
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"); | |||
.set(CLUSTER_ES_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "search_host") | |||
.set(CLUSTER_NODE_ES_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"); | |||
verifyHostIsChecked(settings, of("remote_search_host_1"), "Address in property sonar.cluster.es.hosts is not a valid address: remote_search_host_1"); | |||
verifyHostIsChecked(settings, of("remote_search_host_2"), "Address in property sonar.cluster.es.hosts is not a valid address: remote_search_host_2"); | |||
verifyHostIsChecked(settings, of("search_host"), "Address in property sonar.cluster.node.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())) | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage(expectedMessage); | |||
} | |||
@@ -371,26 +412,28 @@ public class ClusterSettingsTest { | |||
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"); | |||
.set(CLUSTER_ES_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "search_host") | |||
.set(CLUSTER_NODE_ES_HOST.getKey(), "transport_host"); | |||
verifyLoopbackChecked(settings, of("search_host"), "Property sonar.cluster.node.search.host must be a local non-loopback address: search_host"); | |||
verifyLoopbackChecked(settings, of("transport_host"), "Property sonar.cluster.node.es.host must be a local non-loopback address: transport_host"); | |||
verifyLoopbackChecked(settings, of("remote_search_host_1"), "Property sonar.cluster.es.hosts must not contain a loopback address: remote_search_host_1"); | |||
verifyLoopbackChecked(settings, of("remote_search_host_2"), "Property sonar.cluster.es.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"); | |||
"Property sonar.cluster.es.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"); | |||
mockLocalNonLoopback("hz_host", "search_host", "transport_host"); | |||
// will overwrite above move if necessary | |||
hosts.forEach(this::mockLoopback); | |||
assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps())) | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage(expectedMessage); | |||
} | |||
@@ -414,12 +457,11 @@ public class ClusterSettingsTest { | |||
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"); | |||
.set(CLUSTER_ES_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "search_host") | |||
.set(CLUSTER_NODE_ES_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"); | |||
verifyLocalChecked(settings, "search_host", "Property sonar.cluster.node.search.host must be a local non-loopback address: search_host"); | |||
} | |||
private void verifyLocalChecked(TestAppSettings settings, String host, String expectedMessage) { | |||
@@ -429,11 +471,87 @@ public class ClusterSettingsTest { | |||
// will overwrite above move if necessary | |||
mockAllNonLoopback(); | |||
mockNonLocal(host); | |||
assertThatThrownBy(() -> new ClusterSettings(network).accept(settings.getProps())) | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage(expectedMessage); | |||
} | |||
@Test | |||
public void accept_hosts_only_or_hosts_and_ports_only_in_property_SONAR_CLUSTER_ES_HOSTS_of_search_node() { | |||
mockAllHostsValid(); | |||
mockLocalNonLoopback("search_host", "transport_host"); | |||
verifyAllHostsWithPortsOrAllHostsWithoutPortsIsValid(CLUSTER_ES_HOSTS.getKey(), "remote_search_host_1, remote_search_host_2"); | |||
verifyAllHostsWithPortsOrAllHostsWithoutPortsIsValid(CLUSTER_ES_HOSTS.getKey(), "remote_search_host_1:9001, remote_search_host_2:9001"); | |||
} | |||
private void verifyAllHostsWithPortsOrAllHostsWithoutPortsIsValid(String propertyKey, String searchPropertyValue) { | |||
TestAppSettings settings = new TestAppSettings() | |||
.set(CLUSTER_ENABLED.getKey(), "true") | |||
.set(CLUSTER_NODE_TYPE.getKey(), "search") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "search_host") | |||
.set(CLUSTER_NODE_ES_HOST.getKey(), "transport_host") | |||
.set(propertyKey, searchPropertyValue); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatCode(() -> clusterSettings.accept(props)) | |||
.doesNotThrowAnyException(); | |||
} | |||
@Test | |||
public void accept_any_properties_configuration_in_SONAR_CLUSTER_SEARCH_HOSTS_of_search_node() { | |||
mockAllHostsValid(); | |||
mockLocalNonLoopback("search_host", "transport_host"); | |||
verifyAnyHostsConfigurationIsValid("remote_search_host_1, remote_search_host_2"); | |||
verifyAnyHostsConfigurationIsValid("remote_search_host_1:9001, remote_search_host_2:9001"); | |||
verifyAnyHostsConfigurationIsValid("remote_search_host_1, remote_search_host_2:9001"); | |||
verifyAnyHostsConfigurationIsValid("remote_search_host_1, remote_search_host_2"); | |||
} | |||
private void verifyAnyHostsConfigurationIsValid(String searchPropertyValue) { | |||
TestAppSettings settings = new TestAppSettings() | |||
.set(CLUSTER_ENABLED.getKey(), "true") | |||
.set(CLUSTER_NODE_TYPE.getKey(), "search") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "search_host") | |||
.set(CLUSTER_ES_HOSTS.getKey(), "transport_host,transport_host") | |||
.set(CLUSTER_NODE_ES_HOST.getKey(), "transport_host") | |||
.set(CLUSTER_SEARCH_HOSTS.getKey(), searchPropertyValue); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatCode(() -> clusterSettings.accept(props)) | |||
.doesNotThrowAnyException(); | |||
} | |||
@Test | |||
public void ensure_no_mixed_settings_in_ES_HOSTS_in_properties_of_SEARCH_node() { | |||
mockAllHostsValid(); | |||
mockLocalNonLoopback("hz_host", "search_host", "transport_host"); | |||
verifyPortsAreCheckedOnEsNode("remote_search_host_1,remote_search_host_2:9001"); | |||
verifyPortsAreCheckedOnEsNode("remote_search_host_1:9002, remote_search_host_2"); | |||
} | |||
private void verifyPortsAreCheckedOnEsNode(String searchPropertyValue) { | |||
TestAppSettings settings = new TestAppSettings() | |||
.set(CLUSTER_ENABLED.getKey(), "true") | |||
.set(CLUSTER_NODE_TYPE.getKey(), "search") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "search_host") | |||
.set(CLUSTER_NODE_ES_HOST.getKey(), "transport_host") | |||
.set(CLUSTER_ES_HOSTS.getKey(), searchPropertyValue); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage("Entries in property sonar.cluster.es.hosts must not mix 'host:port' and 'host'. Provide hosts without port only or hosts with port only."); | |||
} | |||
private void mockAllNonLoopback() { | |||
when(network.isLoopback(anyString())).thenReturn(false); | |||
} | |||
@@ -485,10 +603,11 @@ public class ClusterSettingsTest { | |||
} | |||
private void assertThatPropertyIsMandatory(TestAppSettings settings, String key) { | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage(format("Property %s is mandatory", key)); | |||
new ClusterSettings(network).accept(settings.getProps()); | |||
ClusterSettings clusterSettings = new ClusterSettings(network); | |||
Props props = settings.getProps(); | |||
assertThatThrownBy(() -> clusterSettings.accept(props)) | |||
.isInstanceOf(MessageException.class) | |||
.hasMessage(format("Property %s is mandatory", key)); | |||
} | |||
private TestAppSettings newSettingsForAppNode(String host) { | |||
@@ -508,8 +627,9 @@ public class ClusterSettingsTest { | |||
.set(CLUSTER_NODE_TYPE.getKey(), "search") | |||
.set(CLUSTER_NODE_HOST.getKey(), host) | |||
.set(CLUSTER_HZ_HOSTS.getKey(), host) | |||
.set(CLUSTER_SEARCH_HOSTS.getKey(), host + ":9001") | |||
.set(SEARCH_HOST.getKey(), host); | |||
.set(CLUSTER_ES_HOSTS.getKey(), host + ":9001") | |||
.set(CLUSTER_NODE_SEARCH_HOST.getKey(), host) | |||
.set(CLUSTER_NODE_ES_HOST.getKey(), host); | |||
} | |||
} |
@@ -29,10 +29,10 @@ import java.io.IOException; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
import java.util.Random; | |||
import javax.annotation.Nullable; | |||
import org.junit.After; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.junit.runner.RunWith; | |||
import org.sonar.application.logging.ListAppender; | |||
@@ -43,14 +43,19 @@ import org.sonar.process.ProcessProperties.Property; | |||
import org.sonar.process.Props; | |||
import org.sonar.process.System2; | |||
import static java.util.Optional.ofNullable; | |||
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_ES_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NAME; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_ES_PORT; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_NAME; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_HOST; | |||
import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_SEARCH_PORT; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_DATA; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_HOME; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_LOGS; | |||
@@ -67,11 +72,10 @@ public class EsSettingsTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private ListAppender listAppender; | |||
private System2 system = mock(System2.class); | |||
private final System2 system = mock(System2.class); | |||
@After | |||
public void tearDown() { | |||
@@ -166,6 +170,10 @@ public class EsSettingsTest { | |||
Props props = new Props(new Properties()); | |||
props.set(SEARCH_PORT.getKey(), "1234"); | |||
props.set(SEARCH_HOST.getKey(), "127.0.0.1"); | |||
props.set(CLUSTER_NODE_SEARCH_HOST.getKey(), "127.0.0.1"); | |||
props.set(CLUSTER_NODE_SEARCH_PORT.getKey(), "1234"); | |||
props.set(CLUSTER_NODE_ES_HOST.getKey(), "127.0.0.1"); | |||
props.set(CLUSTER_NODE_ES_PORT.getKey(), "1234"); | |||
props.set(PATH_HOME.getKey(), homeDir.getAbsolutePath()); | |||
props.set(PATH_DATA.getKey(), temp.newFolder().getAbsolutePath()); | |||
props.set(PATH_TEMP.getKey(), temp.newFolder().getAbsolutePath()); | |||
@@ -177,8 +185,9 @@ public class EsSettingsTest { | |||
EsSettings esSettings = new EsSettings(props, new EsInstallation(props), system); | |||
Map<String, String> generated = esSettings.build(); | |||
assertThat(generated.get("cluster.name")).isEqualTo("sonarqube-1"); | |||
assertThat(generated.get("node.name")).isEqualTo("node-1"); | |||
assertThat(generated) | |||
.containsEntry("cluster.name", "sonarqube-1") | |||
.containsEntry("node.name", "node-1"); | |||
} | |||
@Test | |||
@@ -212,7 +221,7 @@ public class EsSettingsTest { | |||
props.set(PATH_LOGS.getKey(), temp.newFolder().getAbsolutePath()); | |||
EsSettings esSettings = new EsSettings(props, new EsInstallation(props), system); | |||
Map<String, String> generated = esSettings.build(); | |||
assertThat(generated.get("node.name")).isEqualTo("sonarqube"); | |||
assertThat(generated).containsEntry("node.name", "sonarqube"); | |||
} | |||
@Test | |||
@@ -231,29 +240,32 @@ public class EsSettingsTest { | |||
EsSettings underTest = new EsSettings(minProps(new Random().nextBoolean()), mockedEsInstallation, system); | |||
Map<String, String> generated = underTest.build(); | |||
assertThat(generated.get("path.data")).isEqualTo(data.getPath()); | |||
assertThat(generated.get("path.logs")).isEqualTo(log.getPath()); | |||
assertThat(generated) | |||
.containsEntry("path.data", data.getPath()) | |||
.containsEntry("path.logs", log.getPath()); | |||
assertThat(generated.get("path.conf")).isNull(); | |||
} | |||
@Test | |||
public void set_discovery_settings_if_cluster_is_enabled() throws Exception { | |||
Props props = minProps(CLUSTER_ENABLED); | |||
props.set(CLUSTER_SEARCH_HOSTS.getKey(), "1.2.3.4:9000,1.2.3.5:8080"); | |||
props.set(CLUSTER_ES_HOSTS.getKey(), "1.2.3.4:9000,1.2.3.5:8080"); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), system).build(); | |||
assertThat(settings.get("discovery.seed_hosts")).isEqualTo("1.2.3.4:9000,1.2.3.5:8080"); | |||
assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("120s"); | |||
assertThat(settings) | |||
.containsEntry("discovery.seed_hosts", "1.2.3.4:9000,1.2.3.5:8080") | |||
.containsEntry("discovery.initial_state_timeout", "120s"); | |||
} | |||
@Test | |||
public void set_initial_master_nodes_settings_if_cluster_is_enabled() throws Exception { | |||
Props props = minProps(CLUSTER_ENABLED); | |||
props.set(CLUSTER_SEARCH_HOSTS.getKey(), "1.2.3.4:9000,1.2.3.5:8080"); | |||
props.set(CLUSTER_ES_HOSTS.getKey(), "1.2.3.4:9000,1.2.3.5:8080"); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), System2.INSTANCE).build(); | |||
assertThat(settings.get("cluster.initial_master_nodes")).isEqualTo("1.2.3.4:9000,1.2.3.5:8080"); | |||
assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("120s"); | |||
assertThat(settings) | |||
.containsEntry("cluster.initial_master_nodes", "1.2.3.4:9000,1.2.3.5:8080") | |||
.containsEntry("discovery.initial_state_timeout", "120s"); | |||
} | |||
@Test | |||
@@ -262,7 +274,7 @@ public class EsSettingsTest { | |||
props.set(SEARCH_INITIAL_STATE_TIMEOUT.getKey(), "10s"); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), system).build(); | |||
assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("10s"); | |||
assertThat(settings).containsEntry("discovery.initial_state_timeout", "10s"); | |||
} | |||
@Test | |||
@@ -271,7 +283,7 @@ public class EsSettingsTest { | |||
props.set(SEARCH_INITIAL_STATE_TIMEOUT.getKey(), "10s"); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), system).build(); | |||
assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("30s"); | |||
assertThat(settings).containsEntry("discovery.initial_state_timeout", "30s"); | |||
} | |||
@Test | |||
@@ -280,31 +292,32 @@ public class EsSettingsTest { | |||
Props props = minProps(clusterEnabled); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), system).build(); | |||
assertThat(settings.get("http.port")).isEqualTo("9001"); | |||
assertThat(settings.get("http.host")).isEqualTo("127.0.0.1"); | |||
assertThat(settings) | |||
.containsEntry("http.port", "9001") | |||
.containsEntry("http.host", "127.0.0.1"); | |||
} | |||
@Test | |||
@UseDataProvider("clusterEnabledOrNot") | |||
public void enable_http_connector_on_specified_port(boolean clusterEnabled) throws Exception { | |||
String port = "" + new Random().nextInt(49151); | |||
Props props = minProps(clusterEnabled); | |||
props.set(SEARCH_PORT.getKey(), port); | |||
Props props = minProps(clusterEnabled, null, port); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), System2.INSTANCE).build(); | |||
assertThat(settings.get("http.port")).isEqualTo(port); | |||
assertThat(settings.get("http.host")).isEqualTo("127.0.0.1"); | |||
assertThat(settings) | |||
.containsEntry("http.port", port) | |||
.containsEntry("http.host", "127.0.0.1"); | |||
} | |||
@Test | |||
@UseDataProvider("clusterEnabledOrNot") | |||
public void enable_http_connector_different_host(boolean clusterEnabled) throws Exception { | |||
Props props = minProps(clusterEnabled); | |||
props.set(SEARCH_HOST.getKey(), "127.0.0.2"); | |||
Props props = minProps(clusterEnabled, "127.0.0.2", null); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), system).build(); | |||
assertThat(settings.get("http.port")).isEqualTo("9001"); | |||
assertThat(settings.get("http.host")).isEqualTo("127.0.0.2"); | |||
assertThat(settings) | |||
.containsEntry("http.port", "9001") | |||
.containsEntry("http.host", "127.0.0.2"); | |||
} | |||
@Test | |||
@@ -323,7 +336,7 @@ public class EsSettingsTest { | |||
props.set("sonar.search.javaAdditionalOpts", "-Xmx1G -Dbootstrap.system_call_filter=false -Dfoo=bar"); | |||
Map<String, String> settings = new EsSettings(props, new EsInstallation(props), system).build(); | |||
assertThat(settings.get("bootstrap.system_call_filter")).isEqualTo("false"); | |||
assertThat(settings).containsEntry("bootstrap.system_call_filter", "false"); | |||
} | |||
@Test | |||
@@ -357,6 +370,10 @@ public class EsSettingsTest { | |||
} | |||
private Props minProps(boolean cluster) throws IOException { | |||
return minProps(cluster, null, null); | |||
} | |||
private Props minProps(boolean cluster, @Nullable String host, @Nullable String port) throws IOException { | |||
File homeDir = temp.newFolder(); | |||
Props props = new Props(new Properties()); | |||
ServiceLoaderWrapper serviceLoaderWrapper = mock(ServiceLoaderWrapper.class); | |||
@@ -364,6 +381,17 @@ public class EsSettingsTest { | |||
new ProcessProperties(serviceLoaderWrapper).completeDefaults(props); | |||
props.set(PATH_HOME.getKey(), homeDir.getAbsolutePath()); | |||
props.set(Property.CLUSTER_ENABLED.getKey(), Boolean.toString(cluster)); | |||
if (cluster) { | |||
ofNullable(host).ifPresent(h -> props.set(CLUSTER_NODE_ES_HOST.getKey(), h)); | |||
ofNullable(port).ifPresent(p -> props.set(CLUSTER_NODE_ES_PORT.getKey(), p)); | |||
ofNullable(host).ifPresent(h -> props.set(CLUSTER_NODE_SEARCH_HOST.getKey(), h)); | |||
ofNullable(port).ifPresent(p -> props.set(CLUSTER_NODE_SEARCH_PORT.getKey(), p)); | |||
ofNullable(port).ifPresent(h -> props.set(CLUSTER_ES_HOSTS.getKey(), h)); | |||
} else { | |||
ofNullable(host).ifPresent(h -> props.set(SEARCH_HOST.getKey(), h)); | |||
ofNullable(port).ifPresent(p -> props.set(SEARCH_PORT.getKey(), p)); | |||
} | |||
return props; | |||
} | |||
} |
@@ -120,6 +120,13 @@ public class ProcessProperties { | |||
CLUSTER_NAME("sonar.cluster.name", "sonarqube"), | |||
CLUSTER_WEB_STARTUP_LEADER("sonar.cluster.web.startupLeader"), | |||
// search node only settings | |||
CLUSTER_ES_HOSTS("sonar.cluster.es.hosts"), | |||
CLUSTER_NODE_SEARCH_HOST("sonar.cluster.node.search.host"), | |||
CLUSTER_NODE_SEARCH_PORT("sonar.cluster.node.search.port"), | |||
CLUSTER_NODE_ES_HOST("sonar.cluster.node.es.host"), | |||
CLUSTER_NODE_ES_PORT("sonar.cluster.node.es.port"), | |||
AUTH_JWT_SECRET("sonar.auth.jwtBase64Hs256Secret"), | |||
SONAR_WEB_SSO_ENABLE("sonar.web.sso.enable", "false"), | |||
SONAR_WEB_SSO_LOGIN_HEADER("sonar.web.sso.loginHeader", "X-Forwarded-Login"), |