*/
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;
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 {
}
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();
}
.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);
+ }
}
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;
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;
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
this.healthStateSharing = new HealthStateSharingImpl(hzMember, new SearchNodeHealthProvider(settings.getProps(), this, NetworkUtilsImpl.INSTANCE));
this.healthStateSharing.start();
}
+
+ this.esConnector = esConnector;
}
@Override
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;
@Override
public void close() {
+ esConnector.stop();
+
if (hzMember != null) {
if (healthStateSharing != null) {
healthStateSharing.stop();
}
}
+ 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) {
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);
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+ }
+ }
+}
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/*
- * 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();
- }
-}
*/
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;
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;
}
Thread.currentThread().interrupt();
} finally {
if (flag) {
- transportClient.set(null);
+ esConnector.stop();
nodeOperational.set(true);
}
}
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:
}
}
- 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
}
*/
package org.sonar.application.process;
+import com.google.common.net.HostAndPort;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
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;
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);
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 {
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);
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;
@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);
}
@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);
@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())
@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())
@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");
@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");
@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");
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();
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));
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();
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)
);
}
- 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();
command.setJvmOptions(new JvmOptions<>()
.add("-Dfoo=bar")
.add("-Dfoo2=bar2"));
+ command.setEsInstallation(createEsInstallation());
ProcessMonitor monitor = underTest.launch(command);
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;