aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Hartmann <hartmann.eric@gmail.com>2017-04-28 08:55:39 +0200
committerEric Hartmann <hartmann.eric@gmail.Com>2017-05-04 11:07:09 +0200
commitb09e7a4604b75b7e24e9f0543971db1e70d963b4 (patch)
treee8078e42506e6fbc6d27e8a0a136acb50902c2bf
parent5e5eed028e152cc9a8fbda9fdb5c5de255d1b279 (diff)
downloadsonarqube-b09e7a4604b75b7e24e9f0543971db1e70d963b4.tar.gz
sonarqube-b09e7a4604b75b7e24e9f0543971db1e70d963b4.zip
SONAR-9134 Define the minimum number of ES master nodes
In cluster mode, there'll be 2 master nodes by default. In standalone mode, only 1 master node by default A new property sonar.search.initialStateTimeout allows to handle the startup timeout.
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java2
-rw-r--r--server/sonar-search/pom.xml5
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/EsSettings.java33
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/SearchServer.java16
-rw-r--r--server/sonar-search/src/test/java/org/sonar/search/EsSettingsTest.java79
-rw-r--r--server/sonar-search/src/test/java/org/sonar/search/SearchServerTest.java58
6 files changed, 152 insertions, 41 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
index 0f2258f5cf8..959788fb933 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
@@ -67,6 +67,8 @@ public class ProcessProperties {
public static final String SEARCH_JAVA_OPTS = "sonar.search.javaOpts";
public static final String SEARCH_JAVA_ADDITIONAL_OPTS = "sonar.search.javaAdditionalOpts";
public static final String SEARCH_REPLICAS = "sonar.search.replicas";
+ public static final String SEARCH_MINIMUM_MASTER_NODES = "sonar.search.minimumMasterNodes";
+ public static final String SEARCH_INITIAL_STATE_TIMEOUT = "sonar.search.initialStateTimeout";
public static final String WEB_JAVA_OPTS = "sonar.web.javaOpts";
diff --git a/server/sonar-search/pom.xml b/server/sonar-search/pom.xml
index bf21503aa2d..e3e5a3ed316 100644
--- a/server/sonar-search/pom.xml
+++ b/server/sonar-search/pom.xml
@@ -63,6 +63,11 @@
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/server/sonar-search/src/main/java/org/sonar/search/EsSettings.java b/server/sonar-search/src/main/java/org/sonar/search/EsSettings.java
index aae8545f3b1..bb882f38a0d 100644
--- a/server/sonar-search/src/main/java/org/sonar/search/EsSettings.java
+++ b/server/sonar-search/src/main/java/org/sonar/search/EsSettings.java
@@ -117,10 +117,10 @@ public class EsSettings implements EsSettingsMBean {
LOGGER.info("Elasticsearch listening on {}:{}", host, port);
// disable multicast
- builder.put("discovery.zen.ping.multicast.enabled", "false");
- builder.put("transport.tcp.port", port);
- builder.put("transport.host", host.getHostAddress());
- builder.put("network.host", host.getHostAddress());
+ builder.put("discovery.zen.ping.multicast.enabled", "false")
+ .put("transport.tcp.port", port)
+ .put("transport.host", host.getHostAddress())
+ .put("network.host", host.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", true);
@@ -132,11 +132,11 @@ public class EsSettings implements EsSettingsMBean {
} else {
LOGGER.warn("Elasticsearch HTTP connector is enabled on port {}. MUST NOT BE USED FOR PRODUCTION", httpPort);
// see https://github.com/lmenezes/elasticsearch-kopf/issues/195
- builder.put("http.cors.enabled", true);
- builder.put("http.cors.allow-origin", "*");
- builder.put("http.enabled", true);
- builder.put("http.host", host.getHostAddress());
- builder.put("http.port", httpPort);
+ builder.put("http.cors.enabled", true)
+ .put("http.cors.allow-origin", "*")
+ .put("http.enabled", true)
+ .put("http.host", host.getHostAddress())
+ .put("http.port", httpPort);
}
}
@@ -158,20 +158,23 @@ public class EsSettings implements EsSettingsMBean {
}
private void configureCluster(Settings.Builder builder) {
- int replicationFactor = props.valueAsInt(ProcessProperties.SEARCH_REPLICAS, 0);
+ // Default value in a standalone mode, not overridable
+ int replicationFactor = 0;
+ int minimumMasterNodes = 1;
+ String initialStateTimeOut = "30s";
if (clusterEnabled) {
- if (!props.contains(ProcessProperties.SEARCH_REPLICAS)) {
- // In data center edition there is 3 nodes, so if not defined, replicationFactor default value is 1
- replicationFactor = 1;
- }
+ replicationFactor = props.valueAsInt(ProcessProperties.SEARCH_REPLICAS, 1);
+ minimumMasterNodes = props.valueAsInt(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, 2);
+ initialStateTimeOut = props.value(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "120s");
String hosts = props.value(ProcessProperties.CLUSTER_SEARCH_HOSTS, "");
LOGGER.info("Elasticsearch cluster enabled. Connect to hosts [{}]", hosts);
builder.put("discovery.zen.ping.unicast.hosts", hosts);
}
- builder.put("discovery.zen.minimum_master_nodes", 1)
+ builder.put("discovery.zen.minimum_master_nodes", minimumMasterNodes)
+ .put("discovery.initial_state_timeout", initialStateTimeOut)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicationFactor)
.put("cluster.name", getClusterName())
.put("cluster.routing.allocation.awareness.attributes", "rack_id")
diff --git a/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java b/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
index 6929b44212a..21e75193376 100644
--- a/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
+++ b/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
@@ -21,9 +21,12 @@ package org.sonar.search;
import org.apache.lucene.util.StringHelper;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.process.Jmx;
import org.sonar.process.MinimumViableSystem;
import org.sonar.process.Monitored;
@@ -31,7 +34,11 @@ import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.Props;
public class SearchServer implements Monitored {
+ // VisibleForTesting
+ protected static Logger LOGGER = LoggerFactory.getLogger(SearchServer.class);
+ private static final String MIMINUM_MASTER_NODES = "discovery.zen.minimum_master_nodes";
+ private static final String INITIAL_STATE_TIMEOUT = "discovery.initial_state_timeout";
private final EsSettings settings;
private Node node;
@@ -45,7 +52,14 @@ public class SearchServer implements Monitored {
public void start() {
Jmx.register(EsSettingsMBean.OBJECT_NAME, settings);
initBootstrap();
- node = NodeBuilder.nodeBuilder().settings(settings.build()).build();
+ Settings esSettings = settings.build();
+ if (esSettings.getAsInt(MIMINUM_MASTER_NODES, 1) >= 2) {
+ LOGGER.info("Elasticsearch is waiting {} for {} node(s) to be up to start.",
+ esSettings.get(INITIAL_STATE_TIMEOUT),
+ esSettings.get(MIMINUM_MASTER_NODES)
+ );
+ }
+ node = NodeBuilder.nodeBuilder().settings(esSettings).build();
node.start();
}
diff --git a/server/sonar-search/src/test/java/org/sonar/search/EsSettingsTest.java b/server/sonar-search/src/test/java/org/sonar/search/EsSettingsTest.java
index b03f58e55b5..9abbb3248b1 100644
--- a/server/sonar-search/src/test/java/org/sonar/search/EsSettingsTest.java
+++ b/server/sonar-search/src/test/java/org/sonar/search/EsSettingsTest.java
@@ -68,6 +68,8 @@ public class EsSettingsTest {
assertThat(generated.get("index.number_of_replicas")).isEqualTo("0");
assertThat(generated.get("discovery.zen.ping.unicast.hosts")).isNull();
+ assertThat(generated.get("discovery.zen.minimum_master_nodes")).isEqualTo("1");
+ assertThat(generated.get("discovery.initial_state_timeout")).isEqualTo("30s");
}
@Test
@@ -75,7 +77,7 @@ public class EsSettingsTest {
File dataDir = temp.newFolder();
File logDir = temp.newFolder();
File tempDir = temp.newFolder();
- Props props = minProps();
+ Props props = minProps(false);
props.set(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
props.set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
@@ -89,28 +91,75 @@ public class EsSettingsTest {
@Test
public void cluster_is_enabled() throws Exception {
- Props props = minProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+ Props props = minProps(true);
props.set(ProcessProperties.CLUSTER_SEARCH_HOSTS, "1.2.3.4:9000,1.2.3.5:8080");
Settings settings = new EsSettings(props).build();
assertThat(settings.get("index.number_of_replicas")).isEqualTo("1");
assertThat(settings.get("discovery.zen.ping.unicast.hosts")).isEqualTo("1.2.3.4:9000,1.2.3.5:8080");
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("2");
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("120s");
}
@Test
- public void cluster_is_enabled_with_defined_replicas() throws Exception {
- Props props = minProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+ public void incorrect_values_of_minimum_master_nodes() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "ꝱꝲꝳପ");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Value of property sonar.search.minimumMasterNodes is not an integer:");
+ Settings settings = new EsSettings(props).build();
+ }
+
+ @Test
+ public void cluster_is_enabled_with_defined_minimum_master_nodes() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
+ Settings settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("5");
+ }
+
+ @Test
+ public void cluster_is_enabled_with_defined_initialTimeout() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
+ Settings settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("10s");
+ }
+
+ @Test
+ public void in_standalone_initialTimeout_is_not_overridable() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
+ Settings settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("30s");
+ }
+
+ @Test
+ public void in_standalone_minimumMasterNodes_is_not_overridable() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
+ Settings settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("1");
+ }
+
+
+ @Test
+ public void in_standalone_searchReplicas_is_not_overridable() throws Exception {
+ Props props = minProps(false);
props.set(ProcessProperties.SEARCH_REPLICAS, "5");
Settings settings = new EsSettings(props).build();
- assertThat(settings.get("index.number_of_replicas")).isEqualTo("5");
+ assertThat(settings.get("index.number_of_replicas")).isEqualTo("0");
}
@Test
- public void cluster_not_enabled_but_replicas_are_defined() throws Exception {
- Props props = minProps();
+ public void cluster_is_enabled_with_defined_replicas() throws Exception {
+ Props props = minProps(true);
props.set(ProcessProperties.SEARCH_REPLICAS, "5");
Settings settings = new EsSettings(props).build();
@@ -119,7 +168,8 @@ public class EsSettingsTest {
@Test
public void incorrect_values_of_replicas() throws Exception {
- Props props = minProps();
+ Props props = minProps(true);
+
props.set(ProcessProperties.SEARCH_REPLICAS, "ꝱꝲꝳପ");
expectedException.expect(IllegalStateException.class);
@@ -129,7 +179,7 @@ public class EsSettingsTest {
@Test
public void enable_marvel() throws Exception {
- Props props = minProps();
+ Props props = minProps(false);
props.set(EsSettings.PROP_MARVEL_HOSTS, "127.0.0.2,127.0.0.3");
Settings settings = new EsSettings(props).build();
@@ -138,7 +188,7 @@ public class EsSettingsTest {
@Test
public void enable_http_connector() throws Exception {
- Props props = minProps();
+ Props props = minProps(false);
props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
Settings settings = new EsSettings(props).build();
@@ -149,7 +199,7 @@ public class EsSettingsTest {
@Test
public void enable_http_connector_different_host() throws Exception {
- Props props = minProps();
+ Props props = minProps(false);
props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
props.set(ProcessProperties.SEARCH_HOST, "127.0.0.2");
Settings settings = new EsSettings(props).build();
@@ -159,11 +209,12 @@ public class EsSettingsTest {
assertThat(settings.get("http.enabled")).isEqualTo("true");
}
- private Props minProps() throws IOException {
+ private Props minProps(boolean cluster) throws IOException {
File homeDir = temp.newFolder();
Props props = new Props(new Properties());
ProcessProperties.completeDefaults(props);
props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
+ props.set(ProcessProperties.CLUSTER_ENABLED, Boolean.toString(cluster));
return props;
}
}
diff --git a/server/sonar-search/src/test/java/org/sonar/search/SearchServerTest.java b/server/sonar-search/src/test/java/org/sonar/search/SearchServerTest.java
index 5b5fc075f4c..88c1c1960a1 100644
--- a/server/sonar-search/src/test/java/org/sonar/search/SearchServerTest.java
+++ b/server/sonar-search/src/test/java/org/sonar/search/SearchServerTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.search;
+import java.io.IOException;
import java.net.InetAddress;
import java.util.Properties;
import org.elasticsearch.client.Client;
@@ -34,6 +35,7 @@ import org.junit.rules.DisableOnDebug;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
+import org.slf4j.Logger;
import org.sonar.process.Monitored;
import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessEntryPoint;
@@ -42,6 +44,10 @@ import org.sonar.process.Props;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
public class SearchServerTest {
@@ -70,24 +76,41 @@ public class SearchServerTest {
}
@Test
- public void start_stop_server() throws Exception {
- Props props = new Props(new Properties());
- // the following properties have always default values (see ProcessProperties)
- InetAddress host = InetAddress.getLoopbackAddress();
- props.set(ProcessProperties.SEARCH_HOST, host.getHostAddress());
- props.set(ProcessProperties.SEARCH_PORT, String.valueOf(port));
- props.set(ProcessProperties.CLUSTER_NAME, A_CLUSTER_NAME);
- props.set(EsSettings.CLUSTER_SEARCH_NODE_NAME, A_NODE_NAME);
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
- props.set(ProcessEntryPoint.PROPERTY_SHARED_PATH, temp.newFolder().getAbsolutePath());
+ public void log_information_on_startup() throws IOException {
+ Props props = getClusterProperties();
+ props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "2");
+ // Set the timeout to 1sec
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "1s");
+ underTest = new SearchServer(props);
+ Logger logger = mock(Logger.class);
+ underTest.LOGGER = logger;
+ underTest.start();
+ verify(logger).info(eq("Elasticsearch is waiting {} for {} node(s) to be up to start."), eq("1s"), eq("2"));
+ }
+ @Test
+ public void no_log_information_on_startup() throws IOException {
+ Props props = getClusterProperties();
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "1");
+ // Set the timeout to 1sec
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "1s");
underTest = new SearchServer(props);
+ Logger logger = mock(Logger.class);
+ underTest.LOGGER = logger;
+ underTest.start();
+ verify(logger, never()).info(eq("Elasticsearch is waiting {} for {} node(s) to be up to start."), eq("1s"), eq("2"));
+ }
+
+ @Test
+ public void start_stop_server() throws Exception {
+ underTest = new SearchServer(getClusterProperties());
underTest.start();
assertThat(underTest.getStatus()).isEqualTo(Monitored.Status.OPERATIONAL);
Settings settings = Settings.builder().put("cluster.name", A_CLUSTER_NAME).build();
client = TransportClient.builder().settings(settings).build()
- .addTransportAddress(new InetSocketTransportAddress(host, port));
+ .addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), port));
assertThat(client.admin().cluster().prepareClusterStats().get().getStatus()).isEqualTo(ClusterHealthStatus.GREEN);
underTest.stop();
@@ -100,4 +123,17 @@ public class SearchServerTest {
// ok
}
}
+
+ private Props getClusterProperties() throws IOException {
+ Props props = new Props(new Properties());
+ // the following properties have always default values (see ProcessProperties)
+ InetAddress host = InetAddress.getLoopbackAddress();
+ props.set(ProcessProperties.SEARCH_HOST, host.getHostAddress());
+ props.set(ProcessProperties.SEARCH_PORT, String.valueOf(port));
+ props.set(ProcessProperties.CLUSTER_NAME, A_CLUSTER_NAME);
+ props.set(EsSettings.CLUSTER_SEARCH_NODE_NAME, A_NODE_NAME);
+ props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
+ props.set(ProcessEntryPoint.PROPERTY_SHARED_PATH, temp.newFolder().getAbsolutePath());
+ return props;
+ }
}