]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7908 ability to disable Elasticsearch process
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Sat, 30 Jul 2016 08:09:48 +0000 (10:09 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 3 Aug 2016 15:57:17 +0000 (17:57 +0200)
19 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/app/StartupBarrierFactory.java
server/sonar-ce/src/test/java/org/sonar/ce/app/StartupBarrierFactoryTest.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
server/sonar-search/src/main/java/org/sonar/search/EsSettings.java
server/sonar-search/src/test/java/org/sonar/search/EsSettingsTest.java
server/sonar-search/src/test/java/org/sonar/search/SearchServerTest.java
server/sonar-server/src/main/java/org/sonar/server/es/EsClient.java
server/sonar-server/src/main/java/org/sonar/server/es/EsClientProvider.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/es/NewIndex.java
server/sonar-server/src/main/java/org/sonar/server/search/EsSearchModule.java
server/sonar-server/src/test/java/org/sonar/server/es/EsClientProviderTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/es/EsServerHolder.java
server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
server/sonar-server/src/test/java/org/sonar/server/es/NewIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java
server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
sonar-application/src/main/java/org/sonar/application/App.java
sonar-application/src/test/java/org/sonar/application/AppTest.java

index 034a68094cc841c6109043c47576975f1634aa7c..be27bccd6841c5d30e41ae37e832000d0aa648d6 100644 (file)
@@ -25,7 +25,7 @@ import org.sonar.process.ProcessProperties;
 class StartupBarrierFactory {
 
   public StartupBarrier create(ProcessEntryPoint entryPoint) {
-    if (entryPoint.getProps().valueAsBoolean(ProcessProperties.WEB_DISABLED)) {
+    if (entryPoint.getProps().valueAsBoolean(ProcessProperties.CLUSTER_WEB_DISABLED)) {
       return () -> true;
     }
     return new WebServerBarrier(entryPoint.getSharedDir());
index 7d83fa3abfdbd5fb422199b70ad44c82907467c3..ea069d91693bfd1e6e130e2962c166105b0e231c 100644 (file)
@@ -50,7 +50,7 @@ public class StartupBarrierFactoryTest {
 
   @Test
   public void do_not_wait_for_web_server_if_it_is_disabled() {
-    props.set(ProcessProperties.WEB_DISABLED, "true");
+    props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
     StartupBarrier barrier = underTest.create(entryPoint);
 
     assertThat(barrier).isNotInstanceOf(WebServerBarrier.class);
index 151742f4c3773985cacee5ed06ab10ec0ff5c300..89e2fbb2fb27c4a0b0e6b652214caffa739d6747 100644 (file)
@@ -35,6 +35,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
 import org.sonar.process.Props;
 
 import static java.lang.String.valueOf;
@@ -63,7 +64,7 @@ public class ComputeEngineContainerImplTest {
 
   @Test
   public void real_start() throws IOException {
-    Properties properties = new Properties();
+    Properties properties = ProcessProperties.defaults();
     File homeDir = tempFolder.newFolder();
     File dataDir = new File(homeDir, "data");
     File tmpDir = new File(homeDir, "tmp");
@@ -72,10 +73,11 @@ public class ComputeEngineContainerImplTest {
     properties.setProperty(PATH_TEMP, tmpDir.getAbsolutePath());
     properties.setProperty(PROPERTY_PROCESS_INDEX, valueOf(ProcessId.COMPUTE_ENGINE.getIpcIndex()));
     properties.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath());
-    String url = ((BasicDataSource) dbTester.database().getDataSource()).getUrl();
-    properties.setProperty(DatabaseProperties.PROP_URL, url);
+    properties.setProperty(DatabaseProperties.PROP_URL, ((BasicDataSource) dbTester.database().getDataSource()).getUrl());
     properties.setProperty(DatabaseProperties.PROP_USER, "sonar");
     properties.setProperty(DatabaseProperties.PROP_PASSWORD, "sonar");
+
+    // required persisted properties
     insertProperty(CoreProperties.SERVER_ID, "a_startup_id");
     insertProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(new Date()));
 
index 05313f18bb560d8c6c183ea25464206fb9a75798..0a8a46578ded6e07e07ce608792ed54ffea9375d 100644 (file)
@@ -21,18 +21,17 @@ package org.sonar.process;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Properties;
 
 /**
  * Constants shared by search, web server and app processes.
  * They are almost all the properties defined in conf/sonar.properties.
  */
 public class ProcessProperties {
-  public static final String CLUSTER_ACTIVATE = "sonar.cluster.activate";
-  public static final String CLUSTER_MASTER = "sonar.cluster.master";
-  public static final String CLUSTER_MASTER_HOST = "sonar.cluster.masterHost";
-  public static final String CLUSTER_NAME = "sonar.cluster.name";
-  public static final String CLUSTER_NODE_NAME = "sonar.node.name";
+  public static final String CLUSTER_ENABLED = "sonar.cluster.enabled";
   public static final String CLUSTER_CE_DISABLED = "sonar.cluster.ce.disabled";
+  public static final String CLUSTER_SEARCH_DISABLED = "sonar.cluster.search.disabled";
+  public static final String CLUSTER_SEARCH_HOSTS = "sonar.cluster.search.hosts";
   public static final String CLUSTER_WEB_DISABLED = "sonar.cluster.web.disabled";
 
   public static final String JDBC_URL = "sonar.jdbc.url";
@@ -50,6 +49,7 @@ public class ProcessProperties {
   public static final String PATH_TEMP = "sonar.path.temp";
   public static final String PATH_WEB = "sonar.path.web";
 
+  public static final String SEARCH_CLUSTER_NAME = "sonar.search.clusterName";
   public static final String SEARCH_HOST = "sonar.search.host";
   public static final String SEARCH_PORT = "sonar.search.port";
   public static final String SEARCH_HTTP_PORT = "sonar.search.httpPort";
@@ -57,8 +57,8 @@ public class ProcessProperties {
   public static final String SEARCH_JAVA_ADDITIONAL_OPTS = "sonar.search.javaAdditionalOpts";
 
   public static final String WEB_JAVA_OPTS = "sonar.web.javaOpts";
-  public static final String WEB_JAVA_ADDITIONAL_OPTS = "sonar.web.javaAdditionalOpts";
 
+  public static final String WEB_JAVA_ADDITIONAL_OPTS = "sonar.web.javaAdditionalOpts";
   public static final String CE_JAVA_OPTS = "sonar.ce.javaOpts";
   public static final String CE_JAVA_ADDITIONAL_OPTS = "sonar.ce.javaAdditionalOpts";
 
@@ -68,7 +68,7 @@ public class ProcessProperties {
   public static final String ENABLE_STOP_COMMAND = "sonar.enableStopCommand";
 
   public static final String WEB_ENFORCED_JVM_ARGS = "-Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.management.enabled=false " +
-    // jruby is slow with java 8: https://jira.sonarsource.com/browse/SONAR-6115
+  // jruby is slow with java 8: https://jira.sonarsource.com/browse/SONAR-6115
     "-Djruby.compile.invokedynamic=false";
 
   public static final String CE_ENFORCED_JVM_ARGS = "-Djava.awt.headless=true -Dfile.encoding=UTF-8";
@@ -79,8 +79,8 @@ public class ProcessProperties {
 
   public static void completeDefaults(Props props) {
     // init string properties
-    for (Map.Entry<String, String> entry : defaults().entrySet()) {
-      props.setDefault(entry.getKey(), entry.getValue());
+    for (Map.Entry<Object, Object> entry : defaults().entrySet()) {
+      props.setDefault(entry.getKey().toString(), entry.getValue().toString());
     }
 
     // init ports
@@ -97,13 +97,9 @@ public class ProcessProperties {
     }
   }
 
-  public static Map<String, String> defaults() {
-    Map<String, String> defaults = new HashMap<>();
-    defaults.put(ProcessProperties.CLUSTER_NAME, "sonarqube");
-    defaults.put(ProcessProperties.CLUSTER_NODE_NAME, "sonar-" + System.currentTimeMillis());
-    defaults.put(ProcessProperties.CLUSTER_CE_DISABLED, "false");
-    defaults.put(ProcessProperties.CLUSTER_WEB_DISABLED, "false");
-
+  public static Properties defaults() {
+    Properties defaults = new Properties();
+    defaults.put(ProcessProperties.SEARCH_CLUSTER_NAME, "sonarqube");
     defaults.put(ProcessProperties.SEARCH_HOST, "127.0.0.1");
     defaults.put(ProcessProperties.SEARCH_JAVA_OPTS, "-Xmx1G -Xms256m -Xss256k -Djna.nosys=true " +
       "-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly " +
index 950afcfbc76a05cc071843bfa9416d4c8ecb570d..10a25971354e508ccd10d9882fba821bf3891737 100644 (file)
@@ -23,38 +23,41 @@ import java.io.File;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
-import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.UUID;
 import org.apache.commons.lang.StringUtils;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.settings.Settings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.sonar.process.MessageException;
 import org.sonar.process.ProcessProperties;
 import org.sonar.process.Props;
 
 public class EsSettings implements EsSettingsMBean {
 
   private static final Logger LOGGER = LoggerFactory.getLogger(EsSettings.class);
-
   public static final String PROP_MARVEL_HOSTS = "sonar.search.marvelHosts";
+  public static final String CLUSTER_SEARCH_NODE_NAME = "sonar.cluster.search.nodeName";
+  public static final String STANDALONE_NODE_NAME = "sonarqube";
 
   private final Props props;
-  private final Set<String> masterHosts = new LinkedHashSet<>();
+
+  private final boolean clusterEnabled;
+  private final String clusterName;
+  private final String nodeName;
 
   EsSettings(Props props) {
     this.props = props;
-    masterHosts.addAll(Arrays.asList(StringUtils.split(props.value(ProcessProperties.CLUSTER_MASTER_HOST, ""), ",")));
-  }
+    // name of ES cluster must always be set, even if clustering of SQ is disabled
+    this.clusterName = props.nonNullValue(ProcessProperties.SEARCH_CLUSTER_NAME);
 
-  boolean inCluster() {
-    return props.valueAsBoolean(ProcessProperties.CLUSTER_ACTIVATE, false);
-  }
-
-  boolean isMaster() {
-    return props.valueAsBoolean(ProcessProperties.CLUSTER_MASTER, false);
+    this.clusterEnabled = props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED);
+    if (this.clusterEnabled) {
+      this.nodeName = props.value(CLUSTER_SEARCH_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString());
+    } else {
+      this.nodeName = STANDALONE_NODE_NAME;
+    }
   }
 
   @Override
@@ -64,12 +67,12 @@ public class EsSettings implements EsSettingsMBean {
 
   @Override
   public String getClusterName() {
-    return props.value(ProcessProperties.CLUSTER_NAME);
+    return clusterName;
   }
 
   @Override
   public String getNodeName() {
-    return props.value(ProcessProperties.CLUSTER_NODE_NAME);
+    return nodeName;
   }
 
   Settings build() {
@@ -128,8 +131,7 @@ public class EsSettings implements EsSettingsMBean {
       // standard configuration
       builder.put("http.enabled", false);
     } else {
-      LOGGER.warn(String.format(
-        "Elasticsearch HTTP connector is enabled on port %d. MUST NOT BE USED FOR PRODUCTION", httpPort));
+      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.enabled", true);
@@ -157,27 +159,21 @@ public class EsSettings implements EsSettingsMBean {
 
   private void configureCluster(Settings.Builder builder) {
     int replicationFactor = 0;
-    if (inCluster()) {
+    if (clusterEnabled) {
       replicationFactor = 1;
-      if (isMaster()) {
-        LOGGER.info("Elasticsearch cluster enabled. Master node.");
-        builder.put("node.master", true);
-      } else if (!masterHosts.isEmpty()) {
-        LOGGER.info("Elasticsearch cluster enabled. Node connecting to master: {}", masterHosts);
-        builder.put("discovery.zen.ping.unicast.hosts", StringUtils.join(masterHosts, ","));
-        builder.put("node.master", false);
-        builder.put("discovery.zen.minimum_master_nodes", 1);
-      } else {
-        throw new MessageException(String.format("Not an Elasticsearch master nor slave. Please check properties %s and %s",
-          ProcessProperties.CLUSTER_MASTER, ProcessProperties.CLUSTER_MASTER_HOST));
-      }
+      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(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicationFactor);
     builder.put("cluster.name", getClusterName());
     builder.put("cluster.routing.allocation.awareness.attributes", "rack_id");
     String nodeName = getNodeName();
     builder.put("node.rack_id", nodeName);
     builder.put("node.name", nodeName);
+    builder.put("node.data", true);
+    builder.put("node.master", true);
   }
 
   private void configureMarvel(Settings.Builder builder) {
index 35fd0c236ef7c4e11e6ce9a9e8907cffd486ef0d..fb679dea270c674993253b96110fd808ceca164c 100644 (file)
@@ -26,12 +26,10 @@ import org.elasticsearch.common.settings.Settings;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.process.MessageException;
 import org.sonar.process.ProcessProperties;
 import org.sonar.process.Props;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
 
 public class EsSettingsTest {
 
@@ -45,17 +43,17 @@ public class EsSettingsTest {
     props.set(ProcessProperties.SEARCH_PORT, "1234");
     props.set(ProcessProperties.SEARCH_HOST, "127.0.0.1");
     props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
-    props.set(ProcessProperties.CLUSTER_NAME, "tests");
-    props.set(ProcessProperties.CLUSTER_NODE_NAME, "test");
+    props.set(ProcessProperties.SEARCH_CLUSTER_NAME, "sonarqube");
 
     EsSettings esSettings = new EsSettings(props);
-    assertThat(esSettings.inCluster()).isFalse();
 
     Settings generated = esSettings.build();
     assertThat(generated.get("transport.tcp.port")).isEqualTo("1234");
     assertThat(generated.get("transport.host")).isEqualTo("127.0.0.1");
-    assertThat(generated.get("cluster.name")).isEqualTo("tests");
-    assertThat(generated.get("node.name")).isEqualTo("test");
+
+    // no cluster, but cluster and node names are set though
+    assertThat(generated.get("cluster.name")).isEqualTo("sonarqube");
+    assertThat(generated.get("node.name")).isEqualTo("sonarqube");
 
     assertThat(generated.get("path.data")).isNotNull();
     assertThat(generated.get("path.logs")).isNotNull();
@@ -64,24 +62,9 @@ public class EsSettingsTest {
     // http is disabled for security reasons
     assertThat(generated.get("http.enabled")).isEqualTo("false");
 
-    // no cluster, but node name is set though
     assertThat(generated.get("index.number_of_replicas")).isEqualTo("0");
     assertThat(generated.get("discovery.zen.ping.unicast.hosts")).isNull();
   }
-  
-  @Test
-  public void test_default_hosts() throws Exception {
-    Props props = minProps();
-
-    EsSettings esSettings = new EsSettings(props);
-    assertThat(esSettings.inCluster()).isFalse();
-
-    Settings generated = esSettings.build();
-    assertThat(generated.get("transport.tcp.port")).isEqualTo("9001");
-    assertThat(generated.get("transport.host")).isEqualTo("127.0.0.1");
-    assertThat(generated.get("cluster.name")).isEqualTo("sonarqube");
-    assertThat(generated.get("node.name")).startsWith("sonar-");
-  }
 
   @Test
   public void override_dirs() throws Exception {
@@ -101,38 +84,14 @@ public class EsSettingsTest {
   }
 
   @Test
-  public void test_cluster_master() throws Exception {
+  public void cluster_is_enabled() throws Exception {
     Props props = minProps();
-    props.set(ProcessProperties.CLUSTER_ACTIVATE, "true");
-    props.set(ProcessProperties.CLUSTER_MASTER, "true");
+    props.set(ProcessProperties.CLUSTER_ENABLED, "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")).isNull();
-    assertThat(settings.get("node.master")).isEqualTo("true");
-  }
-
-  @Test
-  public void test_cluster_slave() throws Exception {
-    Props props = minProps();
-    props.set(ProcessProperties.CLUSTER_ACTIVATE, "true");
-    props.set(ProcessProperties.CLUSTER_MASTER_HOST, "127.0.0.2,127.0.0.3");
-    Settings settings = new EsSettings(props).build();
-
-    assertThat(settings.get("discovery.zen.ping.unicast.hosts")).isEqualTo("127.0.0.2,127.0.0.3");
-    assertThat(settings.get("node.master")).isEqualTo("false");
-  }
-
-  @Test
-  public void bad_cluster_configuration() throws Exception {
-    Props props = minProps();
-    props.set(ProcessProperties.CLUSTER_ACTIVATE, "true");
-    try {
-      new EsSettings(props).build();
-      fail();
-    } catch (MessageException ignored) {
-      // expected
-    }
+    assertThat(settings.get("discovery.zen.ping.unicast.hosts")).isEqualTo("1.2.3.4:9000,1.2.3.5:8080");
   }
 
   @Test
index 8b63fb52eb4d4037e6e7b95db5b7c0877fa31e6e..60ec649ac60c410fadb7f3c60da42ac0cde0f6ed 100644 (file)
@@ -44,11 +44,8 @@ import static org.junit.Assert.fail;
 
 public class SearchServerTest {
 
-  static final String CLUSTER_NAME = "unitTest";
-
-  int port = NetworkUtils.freePort();
-  SearchServer searchServer;
-  Client client;
+  private static final String A_CLUSTER_NAME = "a_cluster";
+  private static final String A_NODE_NAME = "a_node";
 
   @Rule
   public TestRule timeout = new DisableOnDebug(Timeout.seconds(60));
@@ -56,11 +53,15 @@ public class SearchServerTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
+  private int port = NetworkUtils.freePort();
+  private Client client;
+  private SearchServer underTest;
+
   @After
   public void tearDown() {
-    if (searchServer != null) {
-      searchServer.stop();
-      searchServer.awaitStop();
+    if (underTest != null) {
+      underTest.stop();
+      underTest.awaitStop();
     }
     if (client != null) {
       client.close();
@@ -74,23 +75,23 @@ public class SearchServerTest {
     InetAddress host = InetAddress.getLocalHost();
     props.set(ProcessProperties.SEARCH_HOST, host.getHostAddress());
     props.set(ProcessProperties.SEARCH_PORT, String.valueOf(port));
-    props.set(ProcessProperties.CLUSTER_NAME, CLUSTER_NAME);
-    props.set(ProcessProperties.CLUSTER_NODE_NAME, "test");
+    props.set(ProcessProperties.SEARCH_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());
 
-    searchServer = new SearchServer(props);
-    searchServer.start();
-    assertThat(searchServer.isUp()).isTrue();
+    underTest = new SearchServer(props);
+    underTest.start();
+    assertThat(underTest.isUp()).isTrue();
 
-    Settings settings = Settings.builder().put("cluster.name", CLUSTER_NAME).build();
+    Settings settings = Settings.builder().put("cluster.name", A_CLUSTER_NAME).build();
     client = TransportClient.builder().settings(settings).build()
       .addTransportAddress(new InetSocketTransportAddress(host, port));
     assertThat(client.admin().cluster().prepareClusterStats().get().getStatus()).isEqualTo(ClusterHealthStatus.GREEN);
 
-    searchServer.stop();
-    searchServer.awaitStop();
-    searchServer = null;
+    underTest.stop();
+    underTest.awaitStop();
+    underTest = null;
     try {
       client.admin().cluster().prepareClusterStats().get();
       fail();
index e6c80612c577008f34640d447393e18baa35f9dc..9516ae242e882cb077f281395427578532cca301 100644 (file)
@@ -19,9 +19,6 @@
  */
 package org.sonar.server.es;
 
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import org.apache.commons.lang.StringUtils;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequestBuilder;
 import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequestBuilder;
 import org.elasticsearch.action.admin.cluster.state.ClusterStateRequestBuilder;
@@ -43,20 +40,14 @@ import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.search.SearchScrollRequestBuilder;
 import org.elasticsearch.client.Client;
-import org.elasticsearch.client.transport.TransportClient;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.Priority;
-import org.elasticsearch.common.logging.ESLoggerFactory;
-import org.elasticsearch.common.logging.slf4j.Slf4jESLoggerFactory;
-import org.elasticsearch.common.transport.InetSocketTransportAddress;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.aggregations.AggregationBuilders;
 import org.elasticsearch.search.aggregations.metrics.max.Max;
 import org.picocontainer.Startable;
-import org.sonar.api.config.Settings;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
-import org.sonar.process.ProcessProperties;
 import org.sonar.server.es.request.ProxyBulkRequestBuilder;
 import org.sonar.server.es.request.ProxyClearCacheRequestBuilder;
 import org.sonar.server.es.request.ProxyClusterHealthRequestBuilder;
@@ -77,7 +68,7 @@ import org.sonar.server.es.request.ProxyRefreshRequestBuilder;
 import org.sonar.server.es.request.ProxySearchRequestBuilder;
 import org.sonar.server.es.request.ProxySearchScrollRequestBuilder;
 
-import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
+import static java.util.Objects.requireNonNull;
 
 /**
  * Facade to connect to Elasticsearch node. Handles correctly errors (logging + exceptions
@@ -86,16 +77,11 @@ import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
 public class EsClient implements Startable {
 
   public static final Logger LOGGER = Loggers.get("es");
-  private final Settings settings;
-  private Client nativeClient = null;
 
-  public EsClient(Settings settings) {
-    this.settings = settings;
-  }
+  private final Client nativeClient;
 
-  EsClient(Settings settings, Client nativeClient) {
-    this.settings = settings;
-    this.nativeClient = nativeClient;
+  public EsClient(Client nativeClient) {
+    this.nativeClient = requireNonNull(nativeClient);
   }
 
   public RefreshRequestBuilder prepareRefresh(String... indices) {
@@ -205,28 +191,12 @@ public class EsClient implements Startable {
 
   @Override
   public void start() {
-    if (nativeClient == null) {
-      ESLoggerFactory.setDefaultFactory(new Slf4jESLoggerFactory());
-      org.elasticsearch.common.settings.Settings esSettings = org.elasticsearch.common.settings.Settings.builder()
-        .put("node.name", defaultIfEmpty(settings.getString(ProcessProperties.CLUSTER_NODE_NAME), "sq_local_client"))
-        .put("node.rack_id", defaultIfEmpty(settings.getString(ProcessProperties.CLUSTER_NODE_NAME), "unknown"))
-        .put("cluster.name", StringUtils.defaultIfBlank(settings.getString(ProcessProperties.CLUSTER_NAME), "sonarqube"))
-        .build();
-      nativeClient = TransportClient.builder().settings(esSettings).build();
-      String host = settings.getString(ProcessProperties.SEARCH_HOST);
-      try {
-        ((TransportClient) nativeClient).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), settings.getInt(ProcessProperties.SEARCH_PORT)));
-      } catch (UnknownHostException e) {
-        throw new IllegalStateException("Can not resolve host [" + host + "]", e);
-      }
-    }
+    // nothing to do
   }
 
   @Override
   public void stop() {
-    if (nativeClient != null) {
-      nativeClient.close();
-    }
+    nativeClient.close();
   }
 
   public Client nativeClient() {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/EsClientProvider.java b/server/sonar-server/src/main/java/org/sonar/server/es/EsClientProvider.java
new file mode 100644 (file)
index 0000000..2a4f964
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.es;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import javax.annotation.concurrent.Immutable;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.config.Settings;
+import org.sonar.api.server.ServerSide;
+import org.sonar.process.ProcessProperties;
+
+@ComputeEngineSide
+@ServerSide
+public class EsClientProvider extends ProviderAdapter {
+
+  private EsClient cache;
+
+  public EsClient provide(Settings settings) {
+    if (cache == null) {
+      TransportClient nativeClient;
+      org.elasticsearch.common.settings.Settings.Builder esSettings = org.elasticsearch.common.settings.Settings.builder();
+
+      // mandatory property defined by bootstrap process
+      esSettings.put("cluster.name", settings.getString(ProcessProperties.SEARCH_CLUSTER_NAME));
+
+      boolean clusterEnabled = settings.getBoolean(ProcessProperties.CLUSTER_ENABLED);
+      if (clusterEnabled) {
+        esSettings.put("client.transport.sniff", true);
+        nativeClient = TransportClient.builder().settings(esSettings).build();
+        Arrays.stream(settings.getStringArray(ProcessProperties.CLUSTER_SEARCH_HOSTS))
+          .map(Host::parse)
+          .forEach(h -> h.addTo(nativeClient));
+      } else {
+        nativeClient = TransportClient.builder().settings(esSettings).build();
+        Host host = new Host(settings.getString(ProcessProperties.SEARCH_HOST), settings.getInt(ProcessProperties.SEARCH_PORT));
+        host.addTo(nativeClient);
+      }
+      cache = new EsClient(nativeClient);
+    }
+    return cache;
+  }
+
+  @Immutable
+  private static class Host {
+    private final String ip;
+    private final int port;
+
+    Host(String ip, int port) {
+      this.ip = ip.trim();
+      this.port = port;
+    }
+
+    static Host parse(String s) {
+      String[] split = s.split(":");
+      if (split.length != 2) {
+        throw new IllegalArgumentException("Badly formatted Elasticsearch host: " + s);
+      }
+      return new Host(split[0], Integer.parseInt(split[1]));
+    }
+
+    void addTo(TransportClient client) {
+      try {
+        client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ip), port));
+      } catch (UnknownHostException e) {
+        throw new IllegalStateException("Can not resolve host [" + ip + "]", e);
+      }
+    }
+  }
+}
index dc25828f0453f055986585d2d1bee483651431b0..9ea547d499f78780bf077b6f2b470311cc3447ef 100644 (file)
@@ -74,7 +74,7 @@ public class NewIndex {
   }
 
   public void configureShards(org.sonar.api.config.Settings settings) {
-    boolean clusterMode = settings.getBoolean(ProcessProperties.CLUSTER_ACTIVATE);
+    boolean clusterMode = settings.getBoolean(ProcessProperties.CLUSTER_ENABLED);
     int shards = settings.getInt(format("sonar.search.%s.shards", indexName));
     if (shards == 0) {
       shards = DEFAULT_NUMBER_OF_SHARDS;
index c498264eb8db3dbd5b359e76a5790e7e0fd2b186..2316f75122fc3eaa96ec1ba470c9ccd6869c9cf8 100644 (file)
 package org.sonar.server.search;
 
 import org.sonar.core.platform.Module;
-import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsClientProvider;
 
 public class EsSearchModule extends Module {
   @Override
   protected void configureModule() {
-    add(EsClient.class);
+    add(new EsClientProvider());
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsClientProviderTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsClientProviderTest.java
new file mode 100644 (file)
index 0000000..27e3342
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.es;
+
+import java.net.InetAddress;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.common.transport.TransportAddress;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
+import org.sonar.process.ProcessProperties;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsClientProviderTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Settings settings = new Settings();
+  private EsClientProvider underTest = new EsClientProvider();
+  private String localhost;
+
+  @Before
+  public void setUp() throws Exception {
+    // mandatory property
+    settings.setProperty(ProcessProperties.SEARCH_CLUSTER_NAME, "the_cluster_name");
+
+    localhost = InetAddress.getLocalHost().getHostAddress();
+  }
+
+  @Test
+  public void connection_to_local_es_when_cluster_mode_is_disabled() throws Exception {
+    settings.setProperty(ProcessProperties.CLUSTER_ENABLED, false);
+    settings.setProperty(ProcessProperties.SEARCH_HOST, localhost);
+    settings.setProperty(ProcessProperties.SEARCH_PORT, 8080);
+
+    EsClient client = underTest.provide(settings);
+    TransportClient transportClient = (TransportClient) client.nativeClient();
+    assertThat(transportClient.transportAddresses()).hasSize(1);
+    TransportAddress address = transportClient.transportAddresses().get(0);
+    assertThat(address.getAddress()).isEqualTo(localhost);
+    assertThat(address.getPort()).isEqualTo(8080);
+
+    // keep in cache
+    assertThat(underTest.provide(settings)).isSameAs(client);
+  }
+
+  @Test
+  public void connection_to_remote_es_nodes_when_cluster_mode_is_enabled() throws Exception {
+    settings.setProperty(ProcessProperties.CLUSTER_ENABLED, true);
+    settings.setProperty(ProcessProperties.CLUSTER_SEARCH_HOSTS, format("%s:8080,%s:8081", localhost, localhost));
+
+    EsClient client = underTest.provide(settings);
+    TransportClient transportClient = (TransportClient) client.nativeClient();
+    assertThat(transportClient.transportAddresses()).hasSize(2);
+    TransportAddress address = transportClient.transportAddresses().get(0);
+    assertThat(address.getAddress()).isEqualTo(localhost);
+    assertThat(address.getPort()).isEqualTo(8080);
+    address = transportClient.transportAddresses().get(1);
+    assertThat(address.getAddress()).isEqualTo(localhost);
+    assertThat(address.getPort()).isEqualTo(8081);
+
+    // keep in cache
+    assertThat(underTest.provide(settings)).isSameAs(client);
+  }
+
+  @Test
+  public void fail_if_cluster_host_is_badly_formatted() throws Exception {
+    settings.setProperty(ProcessProperties.CLUSTER_ENABLED, true);
+    settings.setProperty(ProcessProperties.CLUSTER_SEARCH_HOSTS, "missing_colon");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Badly formatted Elasticsearch host: missing_colon");
+    underTest.provide(settings);
+  }
+}
index e1634e7a2920297072daab702b29d9bd66783bab..594cc7f8502fb208db737dade5a1ffef6a5572ab 100644 (file)
@@ -37,16 +37,14 @@ public class EsServerHolder {
 
   private static EsServerHolder HOLDER = null;
   private final String clusterName;
-  private final String nodeName;
   private final int port;
   private final String hostName;
   private final File homeDir;
   private final SearchServer server;
 
-  private EsServerHolder(SearchServer server, String clusterName, String nodeName, int port, String hostName, File homeDir) {
+  private EsServerHolder(SearchServer server, String clusterName, int port, String hostName, File homeDir) {
     this.server = server;
     this.clusterName = clusterName;
-    this.nodeName = nodeName;
     this.port = port;
     this.hostName = hostName;
     this.homeDir = homeDir;
@@ -56,10 +54,6 @@ public class EsServerHolder {
     return clusterName;
   }
 
-  public String getNodeName() {
-    return nodeName;
-  }
-
   public int getPort() {
     return port;
   }
@@ -78,7 +72,6 @@ public class EsServerHolder {
 
   private void reset() {
     TransportClient client = TransportClient.builder().settings(Settings.builder()
-      .put("node.name", nodeName)
       .put("network.bind_host", "localhost")
       .put("cluster.name", clusterName)
       .build()).build();
@@ -104,20 +97,18 @@ public class EsServerHolder {
       homeDir.mkdir();
 
       String clusterName = "testCluster";
-      String nodeName = "test";
       String hostName = "127.0.0.1";
       int port = NetworkUtils.freePort();
 
       Properties properties = new Properties();
-      properties.setProperty(ProcessProperties.CLUSTER_NAME, clusterName);
-      properties.setProperty(ProcessProperties.CLUSTER_NODE_NAME, nodeName);
+      properties.setProperty(ProcessProperties.SEARCH_CLUSTER_NAME, clusterName);
       properties.setProperty(ProcessProperties.SEARCH_PORT, String.valueOf(port));
       properties.setProperty(ProcessProperties.SEARCH_HOST, hostName);
       properties.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
       properties.setProperty(ProcessEntryPoint.PROPERTY_SHARED_PATH, homeDir.getAbsolutePath());
       SearchServer server = new SearchServer(new Props(properties));
       server.start();
-      HOLDER = new EsServerHolder(server, clusterName, nodeName, port, hostName, homeDir);
+      HOLDER = new EsServerHolder(server, clusterName, port, hostName, homeDir);
     }
     HOLDER.reset();
     return HOLDER;
index c839686a3cca0a5948b6e25809b82778f2dc6fd5..6265854bc77500e69e8ac0ef94cb1d274f235ed2 100644 (file)
@@ -55,7 +55,7 @@ import static java.util.Arrays.asList;
 public class EsTester extends ExternalResource {
 
   private final List<IndexDefinition> indexDefinitions;
-  private EsClient client = new EsClient(new Settings(), NodeHolder.INSTANCE.node.client());
+  private EsClient client = new EsClient(NodeHolder.INSTANCE.node.client());
   private ComponentContainer container;
 
   public EsTester(IndexDefinition... defs) {
index b8c2a790bd3acf76c9b69c37029fa48c7235dd56..944a8af591e393050b2fa1b8865809af310ebb66 100644 (file)
@@ -25,6 +25,7 @@ import org.assertj.core.data.MapEntry;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.settings.Settings;
 import org.junit.Test;
+import org.sonar.process.ProcessProperties;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -158,7 +159,7 @@ public class NewIndexTest {
   public void five_shards_and_one_replica_by_default_on_cluster() {
     NewIndex index = new NewIndex("issues");
     org.sonar.api.config.Settings settings = new org.sonar.api.config.Settings();
-    settings.setProperty("sonar.cluster.activate", "true");
+    settings.setProperty(ProcessProperties.CLUSTER_ENABLED, "true");
     index.configureShards(settings);
     assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo(String.valueOf(NewIndex.DEFAULT_NUMBER_OF_SHARDS));
     assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("1");
index bdfbd5edc8271ad121086be0b442e09d3ccd7e6b..20cc59eed0f999e43fe3a1d34883ffe317db8d18 100644 (file)
@@ -62,7 +62,7 @@ public class RuleIndexDefinitionTest {
 
   @Test
   public void enable_replica_if_clustering_is_enabled() {
-    settings.setProperty(ProcessProperties.CLUSTER_ACTIVATE, true);
+    settings.setProperty(ProcessProperties.CLUSTER_ENABLED, true);
     IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
     underTest.define(context);
 
@@ -74,7 +74,7 @@ public class RuleIndexDefinitionTest {
   public void support_long_html_description() throws Exception {
     String longText = StringUtils.repeat("hello  ", 10_000);
     // the following method fails if PUT fails
-    tester.putDocuments(INDEX, RuleIndexDefinition.TYPE_RULE, new RuleDoc(ImmutableMap.<String, Object>of(
+    tester.putDocuments(INDEX, RuleIndexDefinition.TYPE_RULE, new RuleDoc(ImmutableMap.of(
       FIELD_RULE_HTML_DESCRIPTION, longText,
       FIELD_RULE_REPOSITORY, "squid",
       FIELD_RULE_KEY, "squid:S001")));
index 22130902b317b0d153863428b7f512f70cb778c6..2ac7d202daf42e8e2da8aa6c636182db4b7027da 100644 (file)
@@ -102,8 +102,7 @@ public class ServerTester extends ExternalResource {
       Properties properties = new Properties();
       properties.putAll(initialProps);
       esServerHolder = EsServerHolder.get();
-      properties.setProperty(ProcessProperties.CLUSTER_NAME, esServerHolder.getClusterName());
-      properties.setProperty(ProcessProperties.CLUSTER_NODE_NAME, esServerHolder.getNodeName());
+      properties.setProperty(ProcessProperties.SEARCH_CLUSTER_NAME, esServerHolder.getClusterName());
       properties.setProperty(ProcessProperties.SEARCH_PORT, String.valueOf(esServerHolder.getPort()));
       properties.setProperty(ProcessProperties.SEARCH_HOST, String.valueOf(esServerHolder.getHostName()));
       properties.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
index b8f08d18bf3691983d9808e3b2d09ba0f3c42125..8109f0ed0ac0ad5c83235240a708a0395ab8aa74 100644 (file)
@@ -24,7 +24,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
 import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang.StringUtils;
 import org.sonar.process.ProcessId;
 import org.sonar.process.ProcessProperties;
 import org.sonar.process.Props;
@@ -57,22 +56,26 @@ public class App implements Stoppable {
   private static List<JavaCommand> createCommands(Props props) {
     File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
     List<JavaCommand> commands = new ArrayList<>(3);
-    commands.add(createESCommand(props, homeDir));
+    if (isProcessEnabled(props, ProcessProperties.CLUSTER_SEARCH_DISABLED)) {
+      commands.add(createESCommand(props, homeDir));
+    }
 
-    // do not yet start WebServer nor CE on elasticsearch slaves
-    if (StringUtils.isBlank(props.value(ProcessProperties.CLUSTER_MASTER_HOST))) {
-      if (!props.valueAsBoolean(ProcessProperties.CLUSTER_WEB_DISABLED)) {
-        commands.add(createWebServerCommand(props, homeDir));
-      }
+    if (isProcessEnabled(props, ProcessProperties.CLUSTER_WEB_DISABLED)) {
+      commands.add(createWebServerCommand(props, homeDir));
+    }
 
-      if (!props.valueAsBoolean(ProcessProperties.CLUSTER_CE_DISABLED)) {
-        commands.add(createCeServerCommand(props, homeDir));
-      }
+    if (isProcessEnabled(props, ProcessProperties.CLUSTER_CE_DISABLED)) {
+      commands.add(createCeServerCommand(props, homeDir));
     }
 
     return commands;
   }
 
+  private static boolean isProcessEnabled(Props props, String disabledPropertyKey) {
+    return !props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED) ||
+      !props.valueAsBoolean(disabledPropertyKey);
+  }
+
   private static JavaCommand createESCommand(Props props, File homeDir) {
     JavaCommand elasticsearch = new JavaCommand(ProcessId.ELASTICSEARCH);
     elasticsearch
index 5a31d7d8e35bfbb3c53d54fe9cc8b785bf0a2d69..ac2bdf272d39575afa2567e38988c1fcd862453d 100644 (file)
@@ -53,7 +53,7 @@ public class AppTest {
   }
 
   @Test
-  public void start_all_processes_by_default() throws Exception {
+  public void start_all_processes_if_cluster_mode_is_disabled() throws Exception {
     Monitor monitor = mock(Monitor.class);
     App app = new App(monitor);
     Props props = initDefaultProps();
@@ -63,14 +63,50 @@ public class AppTest {
     verify(monitor).start(argument.capture());
 
     assertThat(argument.getValue()).extracting("processId").containsExactly(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
+
+    app.stopAsync();
+  }
+
+  @Test
+  public void start_only_web_server_node_in_cluster() throws Exception {
+    Monitor monitor = mock(Monitor.class);
+    App app = new App(monitor);
+    Props props = initDefaultProps();
+    props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    props.set(ProcessProperties.CLUSTER_CE_DISABLED, "true");
+    props.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+    app.start(props);
+
+    ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor();
+    verify(monitor).start(argument.capture());
+
+    assertThat(argument.getValue()).extracting("processId").containsOnly(ProcessId.WEB_SERVER);
+  }
+
+  @Test
+  public void start_only_compute_engine_node_in_cluster() throws Exception {
+    Monitor monitor = mock(Monitor.class);
+    App app = new App(monitor);
+    Props props = initDefaultProps();
+    props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
+    props.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+    app.start(props);
+
+    ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor();
+    verify(monitor).start(argument.capture());
+
+    assertThat(argument.getValue()).extracting("processId").containsOnly(ProcessId.COMPUTE_ENGINE);
   }
 
   @Test
-  public void do_not_start_WebServer_nor_CE_if_elasticsearch_slave() throws Exception {
+  public void start_only_elasticsearch_node_in_cluster() throws Exception {
     Monitor monitor = mock(Monitor.class);
     App app = new App(monitor);
     Props props = initDefaultProps();
-    props.set("sonar.cluster.masterHost", "1.2.3.4");
+    props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
+    props.set(ProcessProperties.CLUSTER_CE_DISABLED, "true");
     app.start(props);
 
     ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor();