From b93102f205a824959f9a9ba9258f29b04f7fa1a1 Mon Sep 17 00:00:00 2001 From: Eric Hartmann Date: Thu, 19 Apr 2018 18:20:25 +0200 Subject: [PATCH] SONAR-10604 Remove Hazelcast on Search nodes --- .../sonar/application/AppStateFactory.java | 21 ++- .../cluster/ClusterAppStateImpl.java | 19 ++- .../application/config/ClusterSettings.java | 7 + .../{process => es}/EsConnector.java | 6 +- .../sonar/application/es/EsConnectorImpl.java | 141 ++++++++++++++++++ .../application/process/EsConnectorImpl.java | 35 ----- .../application/process/EsProcessMonitor.java | 93 +----------- .../process/ProcessLauncherImpl.java | 9 +- .../application/AppStateFactoryTest.java | 2 + .../cluster/ClusterAppStateImplTest.java | 15 +- .../config/ClusterSettingsTest.java | 42 ++++++ .../process/EsProcessMonitorTest.java | 65 +++----- .../process/ProcessLauncherImplTest.java | 20 ++- 13 files changed, 295 insertions(+), 180 deletions(-) rename server/sonar-main/src/main/java/org/sonar/application/{process => es}/EsConnector.java (84%) create mode 100644 server/sonar-main/src/main/java/org/sonar/application/es/EsConnectorImpl.java delete mode 100644 server/sonar-main/src/main/java/org/sonar/application/process/EsConnectorImpl.java diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java b/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java index 45a5891d3a8..bc92244f749 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java +++ b/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java @@ -19,9 +19,15 @@ */ 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 hostAndPorts = Arrays.stream(searchHosts.split(",")) + .map(HostAndPort::fromString) + .collect(Collectors.toSet()); + return new EsConnectorImpl(props.nonNullValue(CLUSTER_NAME.getKey()), hostAndPorts); + } } diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java index c8bc162e802..0659b0444e3 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java +++ b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java @@ -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 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 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 { @Override public void entryAdded(EntryEvent event) { diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java index 62bb6158381..ddd71cda2fb 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java +++ b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java @@ -148,6 +148,13 @@ public class ClusterSettings implements Consumer { 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 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/process/EsConnector.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsConnector.java similarity index 84% rename from server/sonar-main/src/main/java/org/sonar/application/process/EsConnector.java rename to server/sonar-main/src/main/java/org/sonar/application/es/EsConnector.java index 04e57cd38fa..486591784fa 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/process/EsConnector.java +++ b/server/sonar-main/src/main/java/org/sonar/application/es/EsConnector.java @@ -17,11 +17,11 @@ * 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; +package org.sonar.application.es; -import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.cluster.health.ClusterHealthStatus; public interface EsConnector { - ClusterHealthStatus getClusterHealthStatus(TransportClient transportClient); + 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 index 00000000000..f9df6c56ff9 --- /dev/null +++ b/server/sonar-main/src/main/java/org/sonar/application/es/EsConnectorImpl.java @@ -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 = new AtomicReference<>(null); + private final String clusterName; + private final Set hostAndPorts; + + public EsConnectorImpl(String clusterName, Set 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 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/EsConnectorImpl.java b/server/sonar-main/src/main/java/org/sonar/application/process/EsConnectorImpl.java deleted file mode 100644 index db5c61bc845..00000000000 --- a/server/sonar-main/src/main/java/org/sonar/application/process/EsConnectorImpl.java +++ /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(); - } -} diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java b/server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java index 6dedbe20c7b..be6a0900738 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java +++ b/server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java @@ -19,30 +19,14 @@ */ 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 = 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 } diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java index 32db76d2bbb..1dbb9c36e77 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java +++ b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java @@ -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); diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java index 35a6fec19db..666cecada3c 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java @@ -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); diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java index 262ff716687..bab40cc3e18 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java @@ -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"); diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java index 0f6bf61666e..c79dcd13707 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java @@ -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)); diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/EsProcessMonitorTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/EsProcessMonitorTest.java index 01a18d41abd..82920b4d520 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/process/EsProcessMonitorTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/process/EsProcessMonitorTest.java @@ -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 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 extends AppenderBase { private final List events = new ArrayList(); diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java index 1bf71956a32..388db62f9a4 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java @@ -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 commands = null; private File dir = null; -- 2.39.5