]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10604 Remove Hazelcast on Search nodes
authorEric Hartmann <hartmann.eric@gmail.com>
Thu, 19 Apr 2018 16:20:25 +0000 (18:20 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 10 May 2018 18:20:54 +0000 (20:20 +0200)
14 files changed:
server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java
server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java
server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
server/sonar-main/src/main/java/org/sonar/application/es/EsConnector.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/es/EsConnectorImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/EsConnector.java [deleted file]
server/sonar-main/src/main/java/org/sonar/application/process/EsConnectorImpl.java [deleted file]
server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java
server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java
server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java
server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
server/sonar-main/src/test/java/org/sonar/application/process/EsProcessMonitorTest.java
server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java

index 45a5891d3a84d592e61d203c50a30444fc34a0bb..bc92244f7497443e29e253e68b53ff0fed3ce5ef 100644 (file)
  */
 package org.sonar.application;
 
+import com.google.common.net.HostAndPort;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.sonar.application.cluster.ClusterAppStateImpl;
 import org.sonar.application.config.AppSettings;
 import org.sonar.application.config.ClusterSettings;
+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;
@@ -34,6 +40,8 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HOST;
 import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_NAME;
 import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_PORT;
 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_NAME;
 
 public class AppStateFactory {
 
@@ -44,9 +52,10 @@ public class AppStateFactory {
   }
 
   public AppState create() {
-    if (ClusterSettings.isClusterEnabled(settings)) {
+    if (ClusterSettings.shouldStartHazelcast(settings)) {
+      EsConnector esConnector = createEsConnector(settings.getProps());
       HazelcastMember hzMember = createHzMember(settings.getProps());
-      return new ClusterAppStateImpl(settings, hzMember);
+      return new ClusterAppStateImpl(settings, hzMember, esConnector);
     }
     return new AppStateImpl();
   }
@@ -61,4 +70,12 @@ public class AppStateFactory {
       .setProcessId(ProcessId.APP);
     return builder.build();
   }
+
+  private static EsConnector createEsConnector(Props props) {
+    String searchHosts = props.nonNullValue(CLUSTER_SEARCH_HOSTS.getKey());
+    Set<HostAndPort> hostAndPorts = Arrays.stream(searchHosts.split(","))
+      .map(HostAndPort::fromString)
+      .collect(Collectors.toSet());
+    return new EsConnectorImpl(props.nonNullValue(CLUSTER_NAME.getKey()), hostAndPorts);
+  }
 }
index c8bc162e802378e13add5f1c1c6789dfac8f7ea0..0659b0444e3207ecf895c5deb5e2e5bf4e3599ac 100644 (file)
@@ -35,6 +35,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.locks.Lock;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.application.AppStateListener;
@@ -43,6 +44,7 @@ import org.sonar.application.cluster.health.HealthStateSharingImpl;
 import org.sonar.application.cluster.health.SearchNodeHealthProvider;
 import org.sonar.application.config.AppSettings;
 import org.sonar.application.config.ClusterSettings;
+import org.sonar.application.es.EsConnector;
 import org.sonar.process.MessageException;
 import org.sonar.process.NetworkUtilsImpl;
 import org.sonar.process.ProcessId;
@@ -66,9 +68,10 @@ public class ClusterAppStateImpl implements ClusterAppState {
   private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
   private final String operationalProcessListenerUUID;
   private final String nodeDisconnectedListenerUUID;
+  private final EsConnector esConnector;
   private HealthStateSharing healthStateSharing = null;
 
-  public ClusterAppStateImpl(AppSettings settings, HazelcastMember hzMember) {
+  public ClusterAppStateImpl(AppSettings settings, HazelcastMember hzMember, EsConnector esConnector) {
     this.hzMember = hzMember;
 
     // Get or create the replicated map
@@ -80,6 +83,8 @@ public class ClusterAppStateImpl implements ClusterAppState {
       this.healthStateSharing = new HealthStateSharingImpl(hzMember, new SearchNodeHealthProvider(settings.getProps(), this, NetworkUtilsImpl.INSTANCE));
       this.healthStateSharing.start();
     }
+
+    this.esConnector = esConnector;
   }
 
   @Override
@@ -97,6 +102,11 @@ public class ClusterAppStateImpl implements ClusterAppState {
     if (local) {
       return operationalLocalProcesses.computeIfAbsent(processId, p -> false);
     }
+
+    if (processId.equals(ProcessId.ELASTICSEARCH)) {
+      return isElasticSearchAvailable();
+    }
+
     for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
       if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
         return true;
@@ -194,6 +204,8 @@ public class ClusterAppStateImpl implements ClusterAppState {
 
   @Override
   public void close() {
+    esConnector.stop();
+
     if (hzMember != null) {
       if (healthStateSharing != null) {
         healthStateSharing.stop();
@@ -220,6 +232,11 @@ public class ClusterAppStateImpl implements ClusterAppState {
     }
   }
 
+  private boolean isElasticSearchAvailable() {
+    ClusterHealthStatus clusterHealthStatus = esConnector.getClusterHealthStatus();
+    return clusterHealthStatus.equals(ClusterHealthStatus.GREEN) || clusterHealthStatus.equals(ClusterHealthStatus.YELLOW);
+  }
+
   private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
     @Override
     public void entryAdded(EntryEvent<ClusterProcess, Boolean> event) {
index 62bb615838146667a28450471e103eb4c7eb8eea..ddd71cda2fb620e4dc244834431d70bb67116f01 100644 (file)
@@ -148,6 +148,13 @@ public class ClusterSettings implements Consumer<Props> {
     return props.valueAsBoolean(CLUSTER_ENABLED.getKey());
   }
 
+  /**
+   * Hazelcast must be started when cluster is activated on all nodes but search ones
+   */
+  public static boolean shouldStartHazelcast(AppSettings appSettings) {
+    return isClusterEnabled(appSettings.getProps()) && toNodeType(appSettings.getProps()).equals(NodeType.APPLICATION);
+  }
+
   public static List<ProcessId> getEnabledProcesses(AppSettings settings) {
     if (!isClusterEnabled(settings)) {
       return asList(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsConnector.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsConnector.java
new file mode 100644 (file)
index 0000000..4865917
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.application.es;
+
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+
+public interface EsConnector {
+  ClusterHealthStatus getClusterHealthStatus();
+  void stop();
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsConnectorImpl.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsConnectorImpl.java
new file mode 100644 (file)
index 0000000..f9df6c5
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.application.es;
+
+import com.google.common.net.HostAndPort;
+import io.netty.util.ThreadDeathWatcher;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.common.network.NetworkModule;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.elasticsearch.common.transport.TransportAddress;
+import org.elasticsearch.transport.Netty4Plugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.lang.String.format;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.unmodifiableList;
+import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
+
+public class EsConnectorImpl implements EsConnector {
+
+  private static final Logger LOG = LoggerFactory.getLogger(EsConnectorImpl.class);
+
+  private final AtomicReference<TransportClient> transportClient = new AtomicReference<>(null);
+  private final String clusterName;
+  private final Set<HostAndPort> hostAndPorts;
+
+  public EsConnectorImpl(String clusterName, Set<HostAndPort> hostAndPorts) {
+    this.clusterName = clusterName;
+    this.hostAndPorts = hostAndPorts;
+  }
+
+  @Override
+  public ClusterHealthStatus getClusterHealthStatus() {
+    return getTransportClient().admin().cluster()
+      .health(new ClusterHealthRequest().waitForStatus(ClusterHealthStatus.YELLOW).timeout(timeValueSeconds(30)))
+      .actionGet().getStatus();
+  }
+
+  @Override
+  public void stop() {
+    transportClient.set(null);
+  }
+
+  private TransportClient getTransportClient() {
+    TransportClient res = this.transportClient.get();
+    if (res == null) {
+      res = buildTransportClient();
+      if (this.transportClient.compareAndSet(null, res)) {
+        return res;
+      }
+      return this.transportClient.get();
+    }
+    return res;
+  }
+
+  private TransportClient buildTransportClient() {
+    Settings.Builder esSettings = Settings.builder();
+
+    // mandatory property defined by bootstrap process
+    esSettings.put("cluster.name", clusterName);
+
+    TransportClient nativeClient = new MinimalTransportClient(esSettings.build(), hostAndPorts);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Connected to Elasticsearch node: [{}]", displayedAddresses(nativeClient));
+    }
+    return nativeClient;
+  }
+
+  private static String displayedAddresses(TransportClient nativeClient) {
+    return nativeClient.transportAddresses().stream().map(TransportAddress::toString).collect(Collectors.joining(", "));
+  }
+
+  private static class MinimalTransportClient extends TransportClient {
+
+    public MinimalTransportClient(Settings settings, Set<HostAndPort> hostAndPorts) {
+      super(settings, unmodifiableList(singletonList(Netty4Plugin.class)));
+
+      boolean connectedToOneHost = false;
+      for (HostAndPort hostAndPort : hostAndPorts) {
+        try {
+          addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(hostAndPort.getHostText()), hostAndPort.getPortOrDefault(9001)));
+          connectedToOneHost = true;
+        } catch (UnknownHostException e) {
+          LOG.debug("Can not resolve host [" + hostAndPort.getHostText() + "]", e);
+        }
+      }
+      if (!connectedToOneHost) {
+        throw new IllegalStateException(format("Can not connect to one node from [%s]",
+          hostAndPorts.stream()
+            .map(h -> format("%s:%d", h.getHostText(), h.getPortOrDefault(9001)))
+            .collect(Collectors.joining(","))));
+      }
+    }
+
+    @Override
+    public void close() {
+      super.close();
+      if (!NetworkModule.TRANSPORT_TYPE_SETTING.exists(settings)
+        || NetworkModule.TRANSPORT_TYPE_SETTING.get(settings).equals(Netty4Plugin.NETTY_TRANSPORT_NAME)) {
+        try {
+          GlobalEventExecutor.INSTANCE.awaitInactivity(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+        try {
+          ThreadDeathWatcher.awaitInactivity(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+      }
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/EsConnector.java b/server/sonar-main/src/main/java/org/sonar/application/process/EsConnector.java
deleted file mode 100644 (file)
index 04e57cd..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info 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.application.process;
-
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-
-public interface EsConnector {
-  ClusterHealthStatus getClusterHealthStatus(TransportClient transportClient);
-}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/EsConnectorImpl.java b/server/sonar-main/src/main/java/org/sonar/application/process/EsConnectorImpl.java
deleted file mode 100644 (file)
index db5c61b..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info 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.application.process;
-
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-
-import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
-
-public class EsConnectorImpl implements EsConnector {
-  @Override
-  public ClusterHealthStatus getClusterHealthStatus(TransportClient transportClient) {
-    return transportClient.admin().cluster()
-      .health(new ClusterHealthRequest().waitForStatus(ClusterHealthStatus.YELLOW).timeout(timeValueSeconds(30)))
-      .actionGet().getStatus();
-  }
-}
index 6dedbe20c7b6f2c4fd3b3a0b4d8415111e689a80..be6a0900738e16725396f8c39c2ebbf1ded2473b 100644 (file)
  */
 package org.sonar.application.process;
 
-import com.google.common.net.HostAndPort;
-import io.netty.util.ThreadDeathWatcher;
-import io.netty.util.concurrent.GlobalEventExecutor;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
 import org.elasticsearch.client.transport.NoNodeAvailableException;
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.common.network.NetworkModule;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.transport.InetSocketTransportAddress;
-import org.elasticsearch.common.transport.TransportAddress;
 import org.elasticsearch.discovery.MasterNotDiscoveredException;
-import org.elasticsearch.transport.Netty4Plugin;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.sonar.application.es.EsInstallation;
+import org.sonar.application.es.EsConnector;
 import org.sonar.process.ProcessId;
 
-import static java.util.Collections.singletonList;
-import static java.util.Collections.unmodifiableList;
 import static org.sonar.application.process.EsProcessMonitor.Status.CONNECTION_REFUSED;
 import static org.sonar.application.process.EsProcessMonitor.Status.GREEN;
 import static org.sonar.application.process.EsProcessMonitor.Status.KO;
@@ -57,13 +41,11 @@ public class EsProcessMonitor extends AbstractProcessMonitor {
   private final AtomicBoolean nodeUp = new AtomicBoolean(false);
   private final AtomicBoolean nodeOperational = new AtomicBoolean(false);
   private final AtomicBoolean firstMasterNotDiscoveredLog = new AtomicBoolean(true);
-  private final EsInstallation esConfig;
   private final EsConnector esConnector;
-  private AtomicReference<TransportClient> transportClient = new AtomicReference<>(null);
 
-  public EsProcessMonitor(Process process, ProcessId processId, EsInstallation esConfig, EsConnector esConnector) {
+
+  public EsProcessMonitor(Process process, ProcessId processId, EsConnector esConnector) {
     super(process, processId);
-    this.esConfig = esConfig;
     this.esConnector = esConnector;
   }
 
@@ -81,7 +63,7 @@ public class EsProcessMonitor extends AbstractProcessMonitor {
       Thread.currentThread().interrupt();
     } finally {
       if (flag) {
-        transportClient.set(null);
+        esConnector.stop();
         nodeOperational.set(true);
       }
     }
@@ -103,35 +85,9 @@ public class EsProcessMonitor extends AbstractProcessMonitor {
     return status == YELLOW || status == GREEN;
   }
 
-  static class MinimalTransportClient extends TransportClient {
-
-    MinimalTransportClient(Settings settings) {
-      super(settings, unmodifiableList(singletonList(Netty4Plugin.class)));
-    }
-
-    @Override
-    public void close() {
-      super.close();
-      if (!NetworkModule.TRANSPORT_TYPE_SETTING.exists(settings)
-          || NetworkModule.TRANSPORT_TYPE_SETTING.get(settings).equals(Netty4Plugin.NETTY_TRANSPORT_NAME)) {
-        try {
-          GlobalEventExecutor.INSTANCE.awaitInactivity(5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-          Thread.currentThread().interrupt();
-        }
-        try {
-          ThreadDeathWatcher.awaitInactivity(5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-          Thread.currentThread().interrupt();
-        }
-      }
-    }
-
-  }
-
   private Status checkStatus() {
     try {
-      switch (esConnector.getClusterHealthStatus(getTransportClient())) {
+      switch (esConnector.getClusterHealthStatus()) {
         case GREEN:
           return GREEN;
         case YELLOW:
@@ -154,45 +110,6 @@ public class EsProcessMonitor extends AbstractProcessMonitor {
     }
   }
 
-  private TransportClient getTransportClient() {
-    TransportClient res = this.transportClient.get();
-    if (res == null) {
-      res = buildTransportClient();
-      if (this.transportClient.compareAndSet(null, res)) {
-        return res;
-      }
-      return this.transportClient.get();
-    }
-    return res;
-  }
-
-  private TransportClient buildTransportClient() {
-    Settings.Builder esSettings = Settings.builder();
-
-    // mandatory property defined by bootstrap process
-    esSettings.put("cluster.name", esConfig.getClusterName());
-
-    TransportClient nativeClient = new MinimalTransportClient(esSettings.build());
-    HostAndPort host = HostAndPort.fromParts(esConfig.getHost(), esConfig.getPort());
-    addHostToClient(host, nativeClient);
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Connected to Elasticsearch node: [{}]", displayedAddresses(nativeClient));
-    }
-    return nativeClient;
-  }
-
-  private static void addHostToClient(HostAndPort host, TransportClient client) {
-    try {
-      client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host.getHostText()), host.getPortOrDefault(9001)));
-    } catch (UnknownHostException e) {
-      throw new IllegalStateException("Can not resolve host [" + host + "]", e);
-    }
-  }
-
-  private static String displayedAddresses(TransportClient nativeClient) {
-    return nativeClient.transportAddresses().stream().map(TransportAddress::toString).collect(Collectors.joining(", "));
-  }
-
   enum Status {
     CONNECTION_REFUSED, KO, RED, YELLOW, GREEN
   }
index 32db76d2bbb5564bf410787e665d73f8bf146012..1dbb9c36e7731975fdb645eb8c7a4ab47edb8787 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.application.process;
 
+import com.google.common.net.HostAndPort;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -36,12 +37,15 @@ import org.sonar.application.command.AbstractCommand;
 import org.sonar.application.command.EsScriptCommand;
 import org.sonar.application.command.JavaCommand;
 import org.sonar.application.command.JvmOptions;
+import org.sonar.application.es.EsConnectorImpl;
 import org.sonar.application.es.EsInstallation;
 import org.sonar.process.ProcessId;
 import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
 import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
+import static java.util.Collections.singleton;
 import static java.util.Objects.requireNonNull;
 import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
 import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
@@ -89,7 +93,10 @@ public class ProcessLauncherImpl implements ProcessLauncher {
     ProcessId processId = command.getProcessId();
     try {
       if (processId == ProcessId.ELASTICSEARCH) {
-        return new EsProcessMonitor(process, processId, command.getEsInstallation(), new EsConnectorImpl());
+        EsInstallation esInstallation = command.getEsInstallation();
+        checkArgument(esInstallation != null, "Incorrect configuration EsInstallation is null");
+        EsConnectorImpl esConnector = new EsConnectorImpl(esInstallation.getClusterName(), singleton(HostAndPort.fromParts(esInstallation.getHost(), esInstallation.getPort())));
+        return new EsProcessMonitor(process, processId, esConnector);
       } else {
         ProcessCommands commands = allProcessesCommands.createAfterClean(processId.getIpcIndex());
         return new ProcessCommandsProcessMonitor(process, processId, commands);
index 35a6fec19dbde695938886a497fbc05a9bad5b17..666cecada3ce121582086f729153e4d81179324b 100644 (file)
@@ -34,6 +34,7 @@ import static org.sonar.process.ProcessProperties.Property.CLUSTER_HOSTS;
 import static org.sonar.process.ProcessProperties.Property.CLUSTER_NAME;
 import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_HOST;
 import static org.sonar.process.ProcessProperties.Property.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.Property.CLUSTER_SEARCH_HOSTS;
 
 public class AppStateFactoryTest {
 
@@ -50,6 +51,7 @@ public class AppStateFactoryTest {
     settings.set(CLUSTER_NODE_HOST.getKey(), ip.get().getHostAddress());
     settings.set(CLUSTER_HOSTS.getKey(), ip.get().getHostAddress());
     settings.set(CLUSTER_NAME.getKey(), "foo");
+    settings.set(CLUSTER_SEARCH_HOSTS.getKey(), "localhost:9001");
 
     AppState appState = underTest.create();
     assertThat(appState).isInstanceOf(ClusterAppStateImpl.class);
index 262ff716687b761dd15fd532ce5f6edf0e27c96f..bab40cc3e183b77fcfedda1768fc372b0018275e 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
 import org.sonar.application.AppStateListener;
 import org.sonar.application.config.TestAppSettings;
+import org.sonar.application.es.EsConnector;
 import org.sonar.process.MessageException;
 import org.sonar.process.NetworkUtilsImpl;
 import org.sonar.process.ProcessId;
@@ -52,7 +53,7 @@ public class ClusterAppStateImplTest {
 
   @Test
   public void tryToLockWebLeader_returns_true_only_for_the_first_call() {
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       assertThat(underTest.tryToLockWebLeader()).isEqualTo(true);
       assertThat(underTest.tryToLockWebLeader()).isEqualTo(false);
     }
@@ -61,7 +62,7 @@ public class ClusterAppStateImplTest {
   @Test
   public void test_listeners() {
     AppStateListener listener = mock(AppStateListener.class);
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       underTest.addListener(listener);
 
       underTest.setOperational(ProcessId.ELASTICSEARCH);
@@ -77,7 +78,7 @@ public class ClusterAppStateImplTest {
   @Test
   public void registerSonarQubeVersion_publishes_version_on_first_call() {
 
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       underTest.registerSonarQubeVersion("6.4.1.5");
 
       assertThat(underTest.getHazelcastMember().getAtomicReference(SONARQUBE_VERSION).get())
@@ -87,7 +88,7 @@ public class ClusterAppStateImplTest {
 
   @Test
   public void registerClusterName_publishes_clusterName_on_first_call() {
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       underTest.registerClusterName("foo");
 
       assertThat(underTest.getHazelcastMember().getAtomicReference(CLUSTER_NAME).get())
@@ -97,7 +98,7 @@ public class ClusterAppStateImplTest {
 
   @Test
   public void reset_always_throws_ISE() {
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       expectedException.expect(IllegalStateException.class);
       expectedException.expectMessage("state reset is not supported in cluster mode");
 
@@ -108,7 +109,7 @@ public class ClusterAppStateImplTest {
   @Test
   public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() {
     // Now launch an instance that try to be part of the hzInstance cluster
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       // Register first version
       underTest.getHazelcastMember().getAtomicReference(SONARQUBE_VERSION).set("6.6.0.1111");
 
@@ -122,7 +123,7 @@ public class ClusterAppStateImplTest {
 
   @Test
   public void registerClusterName_throws_MessageException_if_clusterName_is_different() {
-    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember())) {
+    try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(new TestAppSettings(), newHzMember(), mock(EsConnector.class))) {
       // Register first version
       underTest.getHazelcastMember().getAtomicReference(CLUSTER_NAME).set("goodClusterName");
 
index 0f6bf61666e06e4bd1e56bb85380dd7481d95e24..c79dcd137070bb61562ad5ff69172806e02a5fb6 100644 (file)
@@ -174,6 +174,31 @@ public class ClusterSettingsTest {
     assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
   }
 
+  @Test
+  public void shouldStartHazelcast_must_be_true_on_AppNode() {
+    TestAppSettings settings = newSettingsForAppNode();
+
+    assertThat(ClusterSettings.shouldStartHazelcast(settings)).isTrue();
+  }
+
+  @Test
+  public void shouldStartHazelcast_must_be_false_on_SearchNode() {
+    TestAppSettings settings = newSettingsForSearchNode();
+
+    assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
+  }
+
+  @Test
+  public void shouldStartHazelcast_must_be_false_when_cluster_not_activated() {
+    TestAppSettings settings = newSettingsForSearchNode();
+    settings.set(CLUSTER_ENABLED.getKey(), "false");
+    assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
+
+    settings = newSettingsForAppNode();
+    settings.set(CLUSTER_ENABLED.getKey(), "false");
+    assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
+  }
+
   @Test
   public void isLocalElasticsearchEnabled_returns_true_for_a_application_node() {
     TestAppSettings settings = newSettingsForAppNode();
@@ -223,6 +248,23 @@ public class ClusterSettingsTest {
     assertThatPropertyIsMandatory(settings, "sonar.auth.jwtBase64Hs256Secret");
   }
 
+  @Test
+  public void shouldStartHazelcast_should_return_false_when_cluster_not_enabled() {
+    TestAppSettings settings = new TestAppSettings();
+    assertThat(ClusterSettings.shouldStartHazelcast(settings)).isFalse();
+  }
+
+  @Test
+  public void shouldStartHazelcast_should_return_false_on_SearchNode() {
+    assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForSearchNode())).isFalse();
+  }
+
+
+  @Test
+  public void shouldStartHazelcast_should_return_true_on_AppNode() {
+    assertThat(ClusterSettings.shouldStartHazelcast(newSettingsForAppNode())).isTrue();
+  }
+
   private void assertThatPropertyIsMandatory(TestAppSettings settings, String key) {
     expectedException.expect(MessageException.class);
     expectedException.expectMessage(format("Property %s is mandatory", key));
index 01a18d41abd5d19ee72fa31df8ff9f67dd470b20..82920b4d52098db88ff066b9e3f4d59625d5950d 100644 (file)
@@ -23,86 +23,79 @@ import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.LoggerContext;
 import ch.qos.logback.classic.spi.ILoggingEvent;
 import ch.qos.logback.core.AppenderBase;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Properties;
-import java.util.Random;
 import org.elasticsearch.client.transport.NoNodeAvailableException;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.discovery.MasterNotDiscoveredException;
 import org.junit.Test;
 import org.slf4j.LoggerFactory;
-import org.sonar.application.es.EsInstallation;
+import org.sonar.application.es.EsConnector;
 import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class EsProcessMonitorTest {
 
   @Test
-  public void isOperational_should_return_false_if_Elasticsearch_is_RED() throws Exception {
+  public void isOperational_should_return_false_if_Elasticsearch_is_RED() {
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any())).thenReturn(ClusterHealthStatus.RED);
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    when(esConnector.getClusterHealthStatus()).thenReturn(ClusterHealthStatus.RED);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isFalse();
   }
 
   @Test
-  public void isOperational_should_return_true_if_Elasticsearch_is_YELLOW() throws Exception {
+  public void isOperational_should_return_true_if_Elasticsearch_is_YELLOW() {
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any())).thenReturn(ClusterHealthStatus.YELLOW);
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    when(esConnector.getClusterHealthStatus()).thenReturn(ClusterHealthStatus.YELLOW);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isTrue();
   }
 
   @Test
-  public void isOperational_should_return_true_if_Elasticsearch_is_GREEN() throws Exception {
+  public void isOperational_should_return_true_if_Elasticsearch_is_GREEN() {
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any())).thenReturn(ClusterHealthStatus.GREEN);
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    when(esConnector.getClusterHealthStatus()).thenReturn(ClusterHealthStatus.GREEN);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isTrue();
   }
 
   @Test
-  public void isOperational_should_return_true_if_Elasticsearch_was_GREEN_once() throws Exception {
+  public void isOperational_should_return_true_if_Elasticsearch_was_GREEN_once() {
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any())).thenReturn(ClusterHealthStatus.GREEN);
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    when(esConnector.getClusterHealthStatus()).thenReturn(ClusterHealthStatus.GREEN);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isTrue();
 
-    when(esConnector.getClusterHealthStatus(any())).thenReturn(ClusterHealthStatus.RED);
+    when(esConnector.getClusterHealthStatus()).thenReturn(ClusterHealthStatus.RED);
     assertThat(underTest.isOperational()).isTrue();
   }
 
   @Test
-  public void isOperational_should_retry_if_Elasticsearch_is_unreachable() throws Exception {
+  public void isOperational_should_retry_if_Elasticsearch_is_unreachable() {
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any()))
+    when(esConnector.getClusterHealthStatus())
       .thenThrow(new NoNodeAvailableException("test"))
       .thenReturn(ClusterHealthStatus.GREEN);
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isTrue();
   }
 
   @Test
-  public void isOperational_should_return_false_if_Elasticsearch_status_cannot_be_evaluated() throws Exception {
+  public void isOperational_should_return_false_if_Elasticsearch_status_cannot_be_evaluated() {
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any()))
+    when(esConnector.getClusterHealthStatus())
       .thenThrow(new RuntimeException("test"));
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isFalse();
   }
 
   @Test
-  public void isOperational_must_log_once_when_master_is_not_elected() throws Exception {
+  public void isOperational_must_log_once_when_master_is_not_elected() {
     MemoryAppender<ILoggingEvent> memoryAppender = new MemoryAppender<>();
     LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
     lc.reset();
@@ -111,10 +104,10 @@ public class EsProcessMonitorTest {
     lc.getLogger(EsProcessMonitor.class).addAppender(memoryAppender);
 
     EsConnector esConnector = mock(EsConnector.class);
-    when(esConnector.getClusterHealthStatus(any()))
+    when(esConnector.getClusterHealthStatus())
       .thenThrow(new MasterNotDiscoveredException("Master not elected -test-"));
 
-    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, getEsConfig(), esConnector);
+    EsProcessMonitor underTest = new EsProcessMonitor(mock(Process.class), ProcessId.ELASTICSEARCH, esConnector);
     assertThat(underTest.isOperational()).isFalse();
     assertThat(memoryAppender.events).isNotEmpty();
     assertThat(memoryAppender.events)
@@ -132,18 +125,6 @@ public class EsProcessMonitorTest {
       );
   }
 
-  private EsInstallation getEsConfig() throws IOException {
-    Path tempDirectory = Files.createTempDirectory(getClass().getSimpleName());
-    Properties properties = new Properties();
-    properties.setProperty("sonar.path.home", "/imaginary/path");
-    properties.setProperty("sonar.path.data", "/imaginary/path");
-    properties.setProperty("sonar.path.temp", "/imaginary/path");
-    properties.setProperty("sonar.path.logs", "/imaginary/path");
-    return new EsInstallation(new Props(properties))
-      .setHost("localhost")
-      .setPort(new Random().nextInt(40000));
-  }
-
   private class MemoryAppender<E> extends AppenderBase<E> {
     private final List<E> events = new ArrayList();
 
index 1bf71956a32b4dca4a0684d0e0a277ed1025a1fe..388db62f9a442c7eb31a34a12d9e6bffb2896e5d 100644 (file)
@@ -69,6 +69,7 @@ public class ProcessLauncherImplTest {
     command.setJvmOptions(new JvmOptions<>()
       .add("-Dfoo=bar")
       .add("-Dfoo2=bar2"));
+    command.setEsInstallation(createEsInstallation());
 
     ProcessMonitor monitor = underTest.launch(command);
 
@@ -193,10 +194,27 @@ public class ProcessLauncherImplTest {
     command.setEsInstallation(new EsInstallation(props)
       .setEsYmlSettings(mock(EsYmlSettings.class))
       .setEsJvmOptions(mock(EsJvmOptions.class))
-      .setLog4j2Properties(new Properties()));
+      .setLog4j2Properties(new Properties())
+      .setHost("localhost")
+      .setPort(9001)
+      .setClusterName("sonarqube"));
     return command;
   }
 
+  private EsInstallation createEsInstallation() throws IOException {
+    return new EsInstallation(new Props(new Properties())
+      .set("sonar.path.home", temp.newFolder("home").getAbsolutePath())
+      .set("sonar.path.data", temp.newFolder("data").getAbsolutePath())
+      .set("sonar.path.temp", temp.newFolder("temp").getAbsolutePath())
+      .set("sonar.path.logs", temp.newFolder("logs").getAbsolutePath()))
+      .setClusterName("cluster")
+      .setPort(9001)
+      .setHost("localhost")
+      .setEsYmlSettings(new EsYmlSettings(new HashMap<>()))
+      .setEsJvmOptions(new EsJvmOptions())
+      .setLog4j2Properties(new Properties());
+  }
+
   private static class TestProcessBuilder implements ProcessLauncherImpl.ProcessBuilder {
     private List<String> commands = null;
     private File dir = null;