import org.sonar.application.config.AppSettings;
import org.sonar.application.config.ClusterSettings;
import org.sonar.application.process.CommandFactory;
+import org.sonar.application.process.EsCommand;
import org.sonar.application.process.JavaCommand;
import org.sonar.application.process.ProcessLauncher;
import org.sonar.application.process.Lifecycle;
import org.sonar.application.process.ProcessEventListener;
import org.sonar.application.process.ProcessLifecycleListener;
+import org.sonar.application.process.ProcessMonitor;
import org.sonar.application.process.SQProcess;
import org.sonar.process.ProcessId;
private void tryToStartEs() {
SQProcess process = processesById.get(ProcessId.ELASTICSEARCH);
if (process != null) {
- tryToStartProcess(process, commandFactory::createEsCommand);
+ tryToStartEsProcess(process, commandFactory::createEsCommand);
}
}
return;
}
if (appState.isOperational(ProcessId.WEB_SERVER, false)) {
- tryToStartProcess(process, () -> commandFactory.createWebCommand(false));
+ tryToStartJavaProcess(process, () -> commandFactory.createWebCommand(false));
} else if (appState.tryToLockWebLeader()) {
- tryToStartProcess(process, () -> commandFactory.createWebCommand(true));
+ tryToStartJavaProcess(process, () -> commandFactory.createWebCommand(true));
} else {
Optional<String> leader = appState.getLeaderHostName();
if (leader.isPresent()) {
private void tryToStartCe() {
SQProcess process = processesById.get(ProcessId.COMPUTE_ENGINE);
if (process != null && appState.isOperational(ProcessId.WEB_SERVER, false) && isEsClientStartable()) {
- tryToStartProcess(process, commandFactory::createCeCommand);
+ tryToStartJavaProcess(process, commandFactory::createCeCommand);
}
}
return appState.isOperational(ProcessId.ELASTICSEARCH, requireLocalEs);
}
- private void tryToStartProcess(SQProcess process, Supplier<JavaCommand> commandSupplier) {
+ private void tryToStartJavaProcess(SQProcess process, Supplier<JavaCommand> commandSupplier) {
+ tryToStart(process, () -> {
+ JavaCommand command = commandSupplier.get();
+ return processLauncher.launch(command);
+ });
+ }
+
+ private void tryToStartEsProcess(SQProcess process, Supplier<EsCommand> commandSupplier) {
+ tryToStart(process, () -> {
+ EsCommand command = commandSupplier.get();
+ return processLauncher.launch(command);
+ });
+ }
+
+ private void tryToStart(SQProcess process, Supplier<ProcessMonitor> processMonitorSupplier) {
try {
- process.start(() -> {
- JavaCommand command = commandSupplier.get();
- return processLauncher.launch(command);
- });
+ process.start(processMonitorSupplier);
} catch (RuntimeException e) {
// failed to start command -> stop everything
terminate();
public interface CommandFactory {
- JavaCommand createEsCommand();
+ EsCommand createEsCommand();
JavaCommand createWebCommand(boolean leader);
*/
package org.sonar.application.process;
+import java.io.File;
+import java.util.Map;
+import java.util.Optional;
import org.sonar.application.config.AppSettings;
import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
-import java.io.File;
-import java.util.Optional;
-
-import static org.sonar.process.ProcessProperties.*;
+import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST;
+import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT;
+import static org.sonar.process.ProcessProperties.HTTP_PROXY_HOST;
+import static org.sonar.process.ProcessProperties.HTTP_PROXY_PORT;
public class CommandFactoryImpl implements CommandFactory {
/**
}
@Override
- public JavaCommand createEsCommand() {
+ public EsCommand createEsCommand() {
File homeDir = settings.getProps().nonNullValueAsFile(ProcessProperties.PATH_HOME);
- return newJavaCommand(ProcessId.ELASTICSEARCH, homeDir)
- .addJavaOptions("-Djava.awt.headless=true")
- .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
- .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
- .setClassName("org.sonar.search.SearchServer")
- .addClasspath("./lib/common/*")
- .addClasspath("./lib/search/*");
+ File executable = new File(homeDir, getExecutable());
+ if (!executable.exists()) {
+ throw new IllegalStateException("Cannot find elasticsearch binary");
+ }
+
+ Map<String, String> settingsMap = new EsSettings(settings.getProps()).build();
+
+ EsCommand res = new EsCommand(ProcessId.ELASTICSEARCH)
+ .setWorkDir(executable.getParentFile().getParentFile())
+ .setExecutable(executable)
+ .setArguments(settings.getProps().rawProperties())
+ // TODO add argument to specify log4j configuration file
+ // TODO add argument to specify yaml configuration file
+ .setUrl("http://" + settingsMap.get("http.host") + ":" + settingsMap.get("http.port"));
+
+ settingsMap.entrySet().stream()
+ .filter(entry -> !"path.home".equals(entry.getKey()))
+ .forEach(entry -> res.addEsOption("-E" + entry.getKey() + "=" + entry.getValue()));
+
+ return res;
+
+ // FIXME quid of proxy settings and sonar.search.javaOpts/javaAdditionalOpts
+ // defaults of HTTPS are the same than HTTP defaults
+ // setSystemPropertyToDefaultIfNotSet(command, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
+ // setSystemPropertyToDefaultIfNotSet(command, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
+ // command
+ // .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
+ // .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS));
+ }
+
+ private static String getExecutable() {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ return "elasticsearch/bin/elasticsearch.bat";
+ }
+ return "elasticsearch/bin/elasticsearch";
}
@Override
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.process.ProcessId;
+
+public class EsCommand extends AbstractCommand<EsCommand> {
+ private File executable;
+ private String url;
+ private List<String> esOptions = new ArrayList<>();
+
+ public EsCommand(ProcessId id) {
+ super(id);
+ }
+
+ public File getExecutable() {
+ return executable;
+ }
+
+ public EsCommand setExecutable(File executable) {
+ this.executable = executable;
+ return this;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public EsCommand setUrl(String url) {
+ this.url = url;
+ return this;
+ }
+
+ public List<String> getEsOptions() {
+ return esOptions;
+ }
+
+ public EsCommand addEsOption(String s) {
+ if (!s.isEmpty()) {
+ esOptions.add(s);
+ }
+ return this;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 java.io.IOException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EsProcessMonitor extends AbstractProcessMonitor {
+ private static final Logger LOG = LoggerFactory.getLogger(EsProcessMonitor.class);
+ private static final int WAIT_FOR_UP_DELAY_IN_MILLIS = 100;
+ private static final int WAIT_FOR_UP_TIMEOUT = 10 * 60; /* 1min */
+
+ private final AtomicBoolean nodeUp = new AtomicBoolean(false);
+ private final AtomicBoolean nodeOperational = new AtomicBoolean(false);
+ private final URL healthCheckURL;
+
+ public EsProcessMonitor(Process process, String url) throws MalformedURLException {
+ super(process);
+ this.healthCheckURL = new URL(url + "/_cluster/health?wait_for_status=yellow&timeout=30s");
+ }
+
+ @Override
+ public boolean isOperational() {
+ if (nodeOperational.get()) {
+ return true;
+ }
+
+ try {
+ boolean flag = checkOperational();
+ if (flag) {
+ nodeOperational.set(true);
+ }
+ } catch (InterruptedException e) {
+ LOG.trace("Interrupted while checking ES node is operational", e);
+ Thread.currentThread().interrupt();
+ }
+ return nodeOperational.get();
+ }
+
+ private boolean checkOperational() throws InterruptedException {
+ int i = 0;
+ Status status = checkStatus();
+ do {
+ if (status != Status.CONNECTION_REFUSED) {
+ nodeUp.set(true);
+ } else {
+ Thread.sleep(WAIT_FOR_UP_DELAY_IN_MILLIS);
+ i++;
+ status = checkStatus();
+ }
+ } while (!nodeUp.get() && i < WAIT_FOR_UP_TIMEOUT);
+ return status == Status.YELLOW || status == Status.GREEN;
+ }
+
+ private Status checkStatus() {
+ try {
+ URLConnection urlConnection = healthCheckURL.openConnection();
+ urlConnection.connect();
+ String response = IOUtils.toString(urlConnection.getInputStream());
+ if (response.contains("\"status\":\"green\"")) {
+ return Status.GREEN;
+ } else if (response.contains("\"status\":\"yellow\"")) {
+ return Status.YELLOW;
+ } else if (response.contains("\"status\":\"red\"")) {
+ return Status.RED;
+ }
+ return Status.KO;
+ } catch (ConnectException e) {
+ return Status.CONNECTION_REFUSED;
+ } catch (IOException e) {
+ LOG.error("Unexpected error occurred while checking ES node status using WebService API", e);
+ return Status.KO;
+ }
+ }
+
+ enum Status {
+ CONNECTION_REFUSED, KO, RED, YELLOW, GREEN
+ }
+
+ @Override
+ public void askForStop() {
+ process.destroy();
+ }
+
+ @Override
+ public boolean askedForRestart() {
+ // ES does not support asking for restart
+ return false;
+ }
+
+ @Override
+ public void acknowledgeAskForRestart() {
+ // nothing to do
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static java.lang.String.valueOf;
+
+public class EsSettings {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(EsSettings.class);
+ private static final String PROP_MARVEL_HOSTS = "sonar.search.marvelHosts";
+ private static final String CLUSTER_SEARCH_NODE_NAME = "sonar.cluster.search.nodeName";
+ private static final String STANDALONE_NODE_NAME = "sonarqube";
+
+ private final Props props;
+
+ private final boolean clusterEnabled;
+ private final String clusterName;
+ private final String nodeName;
+
+ EsSettings(Props props) {
+ this.props = props;
+
+ this.clusterName = props.nonNullValue(ProcessProperties.CLUSTER_NAME);
+ this.clusterEnabled = props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED);
+ if (this.clusterEnabled) {
+ this.nodeName = props.value(CLUSTER_SEARCH_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString());
+ } else {
+ this.nodeName = STANDALONE_NODE_NAME;
+ }
+ }
+
+ Map<String, String> build() {
+ Map<String, String> builder = new HashMap<>();
+ configureFileSystem(builder);
+ configureNetwork(builder);
+ configureCluster(builder);
+ configureMarvel(builder);
+ return builder;
+ }
+
+ private void configureFileSystem(Map<String, String> builder) {
+ File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
+ File dataDir;
+ File logDir;
+
+ // data dir
+ String dataPath = props.value(ProcessProperties.PATH_DATA);
+ if (StringUtils.isNotEmpty(dataPath)) {
+ dataDir = new File(dataPath, "es");
+ } else {
+ dataDir = new File(homeDir, "data/es");
+ }
+ builder.put("path.data", dataDir.getAbsolutePath());
+
+ String tempPath = props.value(ProcessProperties.PATH_TEMP);
+ builder.put("path.home", new File(tempPath, "es").getAbsolutePath());
+
+ // log dir
+ String logPath = props.value(ProcessProperties.PATH_LOGS);
+ if (StringUtils.isNotEmpty(logPath)) {
+ logDir = new File(logPath);
+ } else {
+ logDir = new File(homeDir, "log");
+ }
+ builder.put("path.logs", logDir.getAbsolutePath());
+ }
+
+ private void configureNetwork(Map<String, String> builder) {
+ InetAddress host = readHost();
+ int port = Integer.parseInt(props.nonNullValue(ProcessProperties.SEARCH_PORT));
+ LOGGER.info("Elasticsearch listening on {}:{}", host, port);
+
+ builder.put("transport.tcp.port", valueOf(port));
+ builder.put("transport.host", valueOf(host.getHostAddress()));
+ builder.put("network.host", valueOf(host.getHostAddress()));
+
+ // Elasticsearch sets the default value of TCP reuse address to true only on non-MSWindows machines, but why ?
+ builder.put("network.tcp.reuse_address", valueOf(true));
+
+ int httpPort = props.valueAsInt(ProcessProperties.SEARCH_HTTP_PORT, -1);
+ if (httpPort < 0) {
+ // standard configuration
+ builder.put("http.enabled", valueOf(false));
+ } else {
+ LOGGER.warn("Elasticsearch HTTP connector is enabled on port {}. MUST NOT BE USED FOR PRODUCTION", httpPort);
+ // see https://github.com/lmenezes/elasticsearch-kopf/issues/195
+ builder.put("http.cors.enabled", valueOf(true));
+ builder.put("http.cors.allow-origin", "*");
+ builder.put("http.enabled", valueOf(true));
+ builder.put("http.host", host.getHostAddress());
+ builder.put("http.port", valueOf(httpPort));
+ }
+ }
+
+ private InetAddress readHost() {
+ String hostProperty = props.nonNullValue(ProcessProperties.SEARCH_HOST);
+ try {
+ return InetAddress.getByName(hostProperty);
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Can not resolve host [" + hostProperty + "]. Please check network settings and property " + ProcessProperties.SEARCH_HOST, e);
+ }
+ }
+
+ void configureIndexDefaults(Map<String, String> builder) {
+ configureIndexDefaultsForCluster(builder);
+ builder.put("index.number_of_shards", "1");
+ builder.put("index.refresh_interval", "30s");
+ builder.put("action.auto_create_index", String.valueOf(false));
+ builder.put("index.mapper.dynamic", String.valueOf(false));
+ }
+
+ private void configureIndexDefaultsForCluster(Map<String, String> builder) {
+ builder.put("index.number_of_replicas", String.valueOf(computeReplicationFactor()));
+ }
+
+ private int computeReplicationFactor() {
+ if (clusterEnabled) {
+ return props.valueAsInt(ProcessProperties.SEARCH_REPLICAS, 1);
+ }
+ return 0;
+ }
+
+ private void configureCluster(Map<String, String> builder) {
+ // Default value in a standalone mode, not overridable
+
+ int minimumMasterNodes = 1;
+ String initialStateTimeOut = "30s";
+
+ if (clusterEnabled) {
+ minimumMasterNodes = props.valueAsInt(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, 2);
+ initialStateTimeOut = props.value(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "120s");
+
+ String hosts = props.value(ProcessProperties.CLUSTER_SEARCH_HOSTS, "");
+ LOGGER.info("Elasticsearch cluster enabled. Connect to hosts [{}]", hosts);
+ builder.put("discovery.zen.ping.unicast.hosts", hosts);
+ }
+
+ builder.put("discovery.zen.minimum_master_nodes", valueOf(minimumMasterNodes));
+ builder.put("discovery.initial_state_timeout", initialStateTimeOut);
+ builder.put("cluster.name", clusterName);
+ builder.put("cluster.routing.allocation.awareness.attributes", "rack_id");
+ builder.put("node.attr.rack_id", nodeName);
+ builder.put("node.name", nodeName);
+ builder.put("node.data", valueOf(true));
+ builder.put("node.master", valueOf(true));
+ }
+
+ private void configureMarvel(Map<String, String> builder) {
+ Set<String> marvels = new TreeSet<>();
+ marvels.addAll(Arrays.asList(StringUtils.split(props.value(PROP_MARVEL_HOSTS, ""), ",")));
+
+ // If we're collecting indexing data send them to the Marvel host(s)
+ if (!marvels.isEmpty()) {
+ String hosts = StringUtils.join(marvels, ",");
+ LOGGER.info("Elasticsearch Marvel is enabled for %s", hosts);
+ builder.put("marvel.agent.exporter.es.hosts", hosts);
+ }
+ }
+
+}
@Override
void close();
+ /**
+ * Launch an ES command.
+ *
+ * @throws IllegalStateException if an error occurs
+ */
+ ProcessMonitor launch(EsCommand esCommand);
+
/**
* Launch a Java command.
*
allProcessesCommands.close();
}
+ @Override
+ public ProcessMonitor launch(EsCommand esCommand) {
+ Process process = null;
+ try {
+ ProcessBuilder processBuilder = create(esCommand);
+ LOG.info("Launch process[{}]: {}", esCommand.getProcessId().getKey(), String.join(" ", processBuilder.command()));
+
+ process = processBuilder.start();
+
+ return new EsProcessMonitor(process, esCommand.getUrl());
+ } catch (Exception e) {
+ // just in case
+ if (process != null) {
+ process.destroyForcibly();
+ }
+ throw new IllegalStateException(format("Fail to launch process [%s]", esCommand.getProcessId().getKey()), e);
+ }
+ }
+
@Override
public ProcessMonitor launch(JavaCommand javaCommand) {
Process process = null;
- ProcessCommands commands;
try {
- commands = allProcessesCommands.createAfterClean(javaCommand.getProcessId().getIpcIndex());
+ ProcessCommands commands = allProcessesCommands.createAfterClean(javaCommand.getProcessId().getIpcIndex());
ProcessBuilder processBuilder = create(javaCommand);
LOG.info("Launch process[{}]: {}", javaCommand.getProcessId().getKey(), String.join(" ", processBuilder.command()));
process = processBuilder.start();
return new ProcessCommandsProcessMonitor(process, commands);
-
} catch (Exception e) {
// just in case
if (process != null) {
}
}
+ private ProcessBuilder create(EsCommand esCommand) {
+ List<String> commands = new ArrayList<>();
+ commands.add(esCommand.getExecutable().getAbsolutePath());
+ commands.addAll(esCommand.getEsOptions());
+
+ return create(esCommand, commands);
+ }
+
private ProcessBuilder create(JavaCommand javaCommand) {
List<String> commands = new ArrayList<>();
commands.add(buildJavaPath());
commands.add(javaCommand.getClassName());
commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
+ return create(javaCommand, commands);
+ }
+
+ private ProcessBuilder create(AbstractCommand<?> javaCommand, List<String> commands) {
ProcessBuilder processBuilder = processBuilderSupplier.get();
processBuilder.command(commands);
processBuilder.directory(javaCommand.getWorkDir());
import org.junit.rules.Timeout;
import org.mockito.Mockito;
import org.sonar.application.config.TestAppSettings;
+import org.sonar.application.process.AbstractCommand;
import org.sonar.application.process.CommandFactory;
+import org.sonar.application.process.EsCommand;
import org.sonar.application.process.JavaCommand;
import org.sonar.application.process.ProcessLauncher;
import org.sonar.application.process.ProcessMonitor;
public class SchedulerImplTest {
- private static final JavaCommand ES_COMMAND = new JavaCommand(ELASTICSEARCH);
+ private static final EsCommand ES_COMMAND = new EsCommand(ELASTICSEARCH);
private static final JavaCommand WEB_LEADER_COMMAND = new JavaCommand(WEB_SERVER);
private static final JavaCommand WEB_FOLLOWER_COMMAND = new JavaCommand(WEB_SERVER);
private static final JavaCommand CE_COMMAND = new JavaCommand(COMPUTE_ENGINE);
private static class TestCommandFactory implements CommandFactory {
@Override
- public JavaCommand createEsCommand() {
+ public EsCommand createEsCommand() {
return ES_COMMAND;
}
private class TestProcessLauncher implements ProcessLauncher {
private final EnumMap<ProcessId, TestProcess> processes = new EnumMap<>(ProcessId.class);
- private final List<JavaCommand> commands = synchronizedList(new ArrayList<>());
+ private final List<AbstractCommand<?>> commands = synchronizedList(new ArrayList<>());
private ProcessId makeStartupFail = null;
+ @Override
+ public ProcessMonitor launch(EsCommand esCommand) {
+ return launchImpl(esCommand);
+ }
+
@Override
public ProcessMonitor launch(JavaCommand javaCommand) {
+ return launchImpl(javaCommand);
+ }
+
+ private ProcessMonitor launchImpl(AbstractCommand<?> javaCommand) {
commands.add(javaCommand);
if (makeStartupFail == javaCommand.getProcessId()) {
throw new IllegalStateException("cannot start " + javaCommand.getProcessId());
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsSettingsTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void test_default_settings() throws Exception {
+ File homeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.SEARCH_PORT, "1234");
+ props.set(ProcessProperties.SEARCH_HOST, "127.0.0.1");
+ props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
+ props.set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+
+ EsSettings esSettings = new EsSettings(props);
+
+ Map<String, String> generated = esSettings.build();
+ assertThat(generated.get("transport.tcp.port")).isEqualTo("1234");
+ assertThat(generated.get("transport.host")).isEqualTo("127.0.0.1");
+
+ // no cluster, but cluster and node names are set though
+ assertThat(generated.get("cluster.name")).isEqualTo("sonarqube");
+ assertThat(generated.get("node.name")).isEqualTo("sonarqube");
+
+ assertThat(generated.get("path.data")).isNotNull();
+ assertThat(generated.get("path.logs")).isNotNull();
+ assertThat(generated.get("path.home")).isNotNull();
+
+ // http is disabled for security reasons
+ assertThat(generated.get("http.enabled")).isEqualTo("false");
+
+ assertThat(generated.get("index.number_of_replicas")).isEqualTo("0");
+ assertThat(generated.get("discovery.zen.ping.unicast.hosts")).isNull();
+ assertThat(generated.get("discovery.zen.minimum_master_nodes")).isEqualTo("1");
+ assertThat(generated.get("discovery.initial_state_timeout")).isEqualTo("30s");
+ }
+
+ @Test
+ public void override_dirs() throws Exception {
+ File dataDir = temp.newFolder();
+ File logDir = temp.newFolder();
+ File tempDir = temp.newFolder();
+ Props props = minProps(false);
+ props.set(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("path.data")).isEqualTo(new File(dataDir, "es").getAbsolutePath());
+ assertThat(settings.get("path.logs")).isEqualTo(logDir.getAbsolutePath());
+ assertThat(settings.get("path.home")).isEqualTo(new File(tempDir, "es").getAbsolutePath());
+ }
+
+ @Test
+ public void cluster_is_enabled() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.CLUSTER_SEARCH_HOSTS, "1.2.3.4:9000,1.2.3.5:8080");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("index.number_of_replicas")).isEqualTo("1");
+ assertThat(settings.get("discovery.zen.ping.unicast.hosts")).isEqualTo("1.2.3.4:9000,1.2.3.5:8080");
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("2");
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("120s");
+ }
+
+ @Test
+ public void incorrect_values_of_minimum_master_nodes() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "ꝱꝲꝳପ");
+
+ EsSettings underTest = new EsSettings(props);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Value of property sonar.search.minimumMasterNodes is not an integer:");
+ underTest.build();
+ }
+
+ @Test
+ public void cluster_is_enabled_with_defined_minimum_master_nodes() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("5");
+ }
+
+ @Test
+ public void cluster_is_enabled_with_defined_initialTimeout() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("10s");
+ }
+
+ @Test
+ public void in_standalone_initialTimeout_is_not_overridable() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("30s");
+ }
+
+ @Test
+ public void in_standalone_minimumMasterNodes_is_not_overridable() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("1");
+ }
+
+
+ @Test
+ public void in_standalone_searchReplicas_is_not_overridable() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_REPLICAS, "5");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("index.number_of_replicas")).isEqualTo("0");
+ }
+
+ @Test
+ public void cluster_is_enabled_with_defined_replicas() throws Exception {
+ Props props = minProps(true);
+ props.set(ProcessProperties.SEARCH_REPLICAS, "5");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("index.number_of_replicas")).isEqualTo("5");
+ }
+
+ @Test
+ public void incorrect_values_of_replicas() throws Exception {
+ Props props = minProps(true);
+
+ props.set(ProcessProperties.SEARCH_REPLICAS, "ꝱꝲꝳପ");
+
+ EsSettings underTest = new EsSettings(props);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Value of property sonar.search.replicas is not an integer:");
+ underTest.build();
+ }
+
+ @Test
+ public void enable_marvel() throws Exception {
+ Props props = minProps(false);
+ props.set("sonar.search.marvelHosts", "127.0.0.2,127.0.0.3");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("marvel.agent.exporter.es.hosts")).isEqualTo("127.0.0.2,127.0.0.3");
+ }
+
+ @Test
+ public void enable_http_connector() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("http.port")).isEqualTo("9010");
+ assertThat(settings.get("http.host")).isEqualTo("127.0.0.1");
+ assertThat(settings.get("http.enabled")).isEqualTo("true");
+ }
+
+ @Test
+ public void enable_http_connector_different_host() throws Exception {
+ Props props = minProps(false);
+ props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
+ props.set(ProcessProperties.SEARCH_HOST, "127.0.0.2");
+ Map<String, String> settings = new EsSettings(props).build();
+
+ assertThat(settings.get("http.port")).isEqualTo("9010");
+ assertThat(settings.get("http.host")).isEqualTo("127.0.0.2");
+ assertThat(settings.get("http.enabled")).isEqualTo("true");
+ }
+
+ private Props minProps(boolean cluster) throws IOException {
+ File homeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ ProcessProperties.completeDefaults(props);
+ props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
+ props.set(ProcessProperties.CLUSTER_ENABLED, Boolean.toString(cluster));
+ return props;
+ }
+}
.forEach(entry -> command.add("-E" + entry.getKey() + "=" + entry.getValue()));
url = "http://"+settingsMap.get("http.host") + ":" + settingsMap.get("http.port");
System.out.println(command.stream().collect(Collectors.joining(" ")));
+
+
+
ProcessBuilder builder = new ProcessBuilder(command)
.directory(new File(path.getParent().toAbsolutePath().toString()));
builder.redirectOutput(ProcessBuilder.Redirect.PIPE);