<artifactId>hazelcast-client</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.tngtech.java</groupId>
+ <artifactId>junit-dataprovider</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
import org.sonar.application.process.ProcessMonitor;
import org.sonar.application.process.SQProcess;
import org.sonar.process.ProcessId;
-import org.sonar.process.command.CommandFactory;
-import org.sonar.process.command.EsCommand;
-import org.sonar.process.command.JavaCommand;
+import org.sonar.application.command.CommandFactory;
+import org.sonar.application.command.EsCommand;
+import org.sonar.application.command.JavaCommand;
public class SchedulerImpl implements Scheduler, ProcessEventListener, ProcessLifecycleListener, AppStateListener {
--- /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.command;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.sonar.process.ProcessId;
+import org.sonar.process.System2;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class AbstractCommand<T extends AbstractCommand> {
+ // unique key among the group of commands to launch
+ private final ProcessId id;
+ // program arguments
+ private final Map<String, String> arguments = new LinkedHashMap<>();
+ private final Map<String, String> envVariables;
+ private final Set<String> suppressedEnvVariables = new HashSet<>();
+ private final File workDir;
+
+ protected AbstractCommand(ProcessId id, File workDir, System2 system2) {
+ this.id = requireNonNull(id, "ProcessId can't be null");
+ this.workDir = requireNonNull(workDir, "workDir can't be null");
+ this.envVariables = new HashMap<>(system2.getenv());
+ }
+
+ public ProcessId getProcessId() {
+ return id;
+ }
+
+ public File getWorkDir() {
+ return workDir;
+ }
+
+ @SuppressWarnings("unchecked")
+ private T castThis() {
+ return (T) this;
+ }
+
+ public Map<String, String> getArguments() {
+ return arguments;
+ }
+
+ public T setArgument(String key, @Nullable String value) {
+ if (value == null) {
+ arguments.remove(key);
+ } else {
+ arguments.put(key, value);
+ }
+ return castThis();
+ }
+
+ public T setArguments(Properties args) {
+ for (Map.Entry<Object, Object> entry : args.entrySet()) {
+ setArgument(entry.getKey().toString(), entry.getValue() != null ? entry.getValue().toString() : null);
+ }
+ return castThis();
+ }
+
+ public Map<String, String> getEnvVariables() {
+ return envVariables;
+ }
+
+ public Set<String> getSuppressedEnvVariables() {
+ return suppressedEnvVariables;
+ }
+
+ public T suppressEnvVariable(String key) {
+ requireNonNull(key, "key can't be null");
+ suppressedEnvVariables.add(key);
+ envVariables.remove(key);
+ return castThis();
+ }
+
+ public T setEnvVariable(String key, String value) {
+ envVariables.put(
+ requireNonNull(key, "key can't be null"),
+ requireNonNull(value, "value can't be null"));
+ return castThis();
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class CeJvmOptions extends JvmOptions<CeJvmOptions> {
+ public CeJvmOptions(File tmpDir) {
+ super(mandatoryOptions(tmpDir));
+ }
+
+ private static Map<String, String> mandatoryOptions(File tmpDir) {
+ Map<String, String> res = new LinkedHashMap<>(3);
+ res.put("-Djava.awt.headless=", "true");
+ res.put("-Dfile.encoding=", "UTF-8");
+ res.put("-Djava.io.tmpdir=", tmpDir.getAbsolutePath());
+ return res;
+ }
+}
--- /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.command;
+
+public interface CommandFactory {
+
+ EsCommand createEsCommand();
+
+ JavaCommand createWebCommand(boolean leader);
+
+ JavaCommand createCeCommand();
+
+}
--- /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.command;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Optional;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+import org.sonar.process.System2;
+import org.sonar.application.es.EsFileSystem;
+import org.sonar.application.es.EsLogging;
+import org.sonar.application.es.EsSettings;
+import org.sonar.application.es.EsYmlSettings;
+
+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 {
+ private static final String ENV_VAR_JAVA_TOOL_OPTIONS = "JAVA_TOOL_OPTIONS";
+ /**
+ * Properties about proxy that must be set as system properties
+ */
+ private static final String[] PROXY_PROPERTY_KEYS = new String[] {
+ HTTP_PROXY_HOST,
+ HTTP_PROXY_PORT,
+ "http.nonProxyHosts",
+ HTTPS_PROXY_HOST,
+ HTTPS_PROXY_PORT,
+ "http.auth.ntlm.domain",
+ "socksProxyHost",
+ "socksProxyPort"};
+
+ private final Props props;
+ private final File tempDir;
+
+ public CommandFactoryImpl(Props props, File tempDir, System2 system2) {
+ this.props = props;
+ this.tempDir = tempDir;
+ String javaToolOptions = system2.getenv(ENV_VAR_JAVA_TOOL_OPTIONS);
+ if (javaToolOptions != null && !javaToolOptions.trim().isEmpty()) {
+ LoggerFactory.getLogger(CommandFactoryImpl.class)
+ .warn("JAVA_TOOL_OPTIONS is defined but will be ignored. " +
+ "Use properties sonar.*.javaOpts and/or sonar.*.javaAdditionalOpts in sonar.properties to change SQ JVM processes options");
+ }
+ }
+
+ @Override
+ public EsCommand createEsCommand() {
+ EsFileSystem esFileSystem = new EsFileSystem(props);
+ if (!esFileSystem.getExecutable().exists()) {
+ throw new IllegalStateException("Cannot find elasticsearch binary");
+ }
+ Map<String, String> settingsMap = new EsSettings(props, esFileSystem, System2.INSTANCE).build();
+
+ return new EsCommand(ProcessId.ELASTICSEARCH, esFileSystem.getHomeDirectory())
+ .setFileSystem(esFileSystem)
+ .setLog4j2Properties(new EsLogging().createProperties(props, esFileSystem.getLogDirectory()))
+ .setArguments(props.rawProperties())
+ .setClusterName(settingsMap.get("cluster.name"))
+ .setHost(settingsMap.get("network.host"))
+ .setPort(Integer.valueOf(settingsMap.get("transport.tcp.port")))
+ .addEsOption("-Epath.conf=" + esFileSystem.getConfDirectory().getAbsolutePath())
+ .setEsJvmOptions(new EsJvmOptions()
+ .addFromMandatoryProperty(props, ProcessProperties.SEARCH_JAVA_OPTS)
+ .addFromMandatoryProperty(props, ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
+ .setEsYmlSettings(new EsYmlSettings(settingsMap))
+ .setEnvVariable("ES_JVM_OPTIONS", esFileSystem.getJvmOptions().getAbsolutePath())
+ .setEnvVariable("JAVA_HOME", System.getProperties().getProperty("java.home"))
+ .suppressEnvVariable(ENV_VAR_JAVA_TOOL_OPTIONS);
+ }
+
+ @Override
+ public JavaCommand createWebCommand(boolean leader) {
+ File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
+
+ WebJvmOptions jvmOptions = new WebJvmOptions(tempDir)
+ .addFromMandatoryProperty(props, ProcessProperties.WEB_JAVA_OPTS)
+ .addFromMandatoryProperty(props, ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS);
+ addProxyJvmOptions(jvmOptions);
+
+ JavaCommand<WebJvmOptions> command = new JavaCommand<WebJvmOptions>(ProcessId.WEB_SERVER, homeDir)
+ .setArguments(props.rawProperties())
+ .setJvmOptions(jvmOptions)
+ // required for logback tomcat valve
+ .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS))
+ .setArgument("sonar.cluster.web.startupLeader", Boolean.toString(leader))
+ .setClassName("org.sonar.server.app.WebServer")
+ .addClasspath("./lib/common/*")
+ .addClasspath("./lib/server/*");
+ String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
+ if (driverPath != null) {
+ command.addClasspath(driverPath);
+ }
+ command.suppressEnvVariable(ENV_VAR_JAVA_TOOL_OPTIONS);
+ return command;
+ }
+
+ @Override
+ public JavaCommand createCeCommand() {
+ File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
+
+ CeJvmOptions jvmOptions = new CeJvmOptions(tempDir)
+ .addFromMandatoryProperty(props, ProcessProperties.CE_JAVA_OPTS)
+ .addFromMandatoryProperty(props, ProcessProperties.CE_JAVA_ADDITIONAL_OPTS);
+ addProxyJvmOptions(jvmOptions);
+
+ JavaCommand<CeJvmOptions> command = new JavaCommand<CeJvmOptions>(ProcessId.COMPUTE_ENGINE, homeDir)
+ .setArguments(props.rawProperties())
+ .setJvmOptions(jvmOptions)
+ .setClassName("org.sonar.ce.app.CeServer")
+ .addClasspath("./lib/common/*")
+ .addClasspath("./lib/server/*")
+ .addClasspath("./lib/ce/*");
+ String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
+ if (driverPath != null) {
+ command.addClasspath(driverPath);
+ }
+ command.suppressEnvVariable(ENV_VAR_JAVA_TOOL_OPTIONS);
+ return command;
+ }
+
+ private <T extends JvmOptions> void addProxyJvmOptions(JvmOptions<T> jvmOptions) {
+ for (String key : PROXY_PROPERTY_KEYS) {
+ getPropsValue(key).ifPresent(val -> jvmOptions.add("-D" + key + "=" + val));
+ }
+
+ // defaults of HTTPS are the same than HTTP defaults
+ setSystemPropertyToDefaultIfNotSet(jvmOptions, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
+ setSystemPropertyToDefaultIfNotSet(jvmOptions, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
+ }
+
+ private void setSystemPropertyToDefaultIfNotSet(JvmOptions jvmOptions,
+ String httpsProperty, String httpProperty) {
+ Optional<String> httpValue = getPropsValue(httpProperty);
+ Optional<String> httpsValue = getPropsValue(httpsProperty);
+ if (!httpsValue.isPresent() && httpValue.isPresent()) {
+ jvmOptions.add("-D" + httpsProperty + "=" + httpValue.get());
+ }
+ }
+
+ private Optional<String> getPropsValue(String key) {
+ return Optional.ofNullable(props.value(key));
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.sonar.application.es.EsFileSystem;
+import org.sonar.application.es.EsYmlSettings;
+import org.sonar.process.ProcessId;
+import org.sonar.process.System2;
+
+public class EsCommand extends AbstractCommand<EsCommand> {
+ private EsFileSystem fileSystem;
+ private String clusterName;
+ private String host;
+ private int port;
+ private Properties log4j2Properties;
+ private List<String> esOptions = new ArrayList<>();
+ private EsJvmOptions esJvmOptions;
+ private EsYmlSettings esYmlSettings;
+
+ public EsCommand(ProcessId id, File workDir) {
+ super(id, workDir, System2.INSTANCE);
+ }
+
+ public EsFileSystem getFileSystem() {
+ return fileSystem;
+ }
+
+ public EsCommand setFileSystem(EsFileSystem fileSystem) {
+ this.fileSystem = fileSystem;
+ return this;
+ }
+
+ public String getClusterName() {
+ return clusterName;
+ }
+
+ public EsCommand setClusterName(String clusterName) {
+ this.clusterName = clusterName;
+ return this;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public EsCommand setHost(String host) {
+ this.host = host;
+ return this;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public EsCommand setPort(int port) {
+ this.port = port;
+ return this;
+ }
+
+ public Properties getLog4j2Properties() {
+ return log4j2Properties;
+ }
+
+ public EsCommand setLog4j2Properties(Properties log4j2Properties) {
+ this.log4j2Properties = log4j2Properties;
+ return this;
+ }
+
+ public List<String> getEsOptions() {
+ return esOptions;
+ }
+
+ public EsCommand addEsOption(String s) {
+ if (!s.isEmpty()) {
+ esOptions.add(s);
+ }
+ return this;
+ }
+
+ public EsCommand setEsJvmOptions(EsJvmOptions esJvmOptions) {
+ this.esJvmOptions = esJvmOptions;
+ return this;
+ }
+
+ public EsJvmOptions getEsJvmOptions() {
+ return esJvmOptions;
+ }
+
+ public EsCommand setEsYmlSettings(EsYmlSettings esYmlSettings) {
+ this.esYmlSettings = esYmlSettings;
+ return this;
+ }
+
+ public EsYmlSettings getEsYmlSettings() {
+ return esYmlSettings;
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class EsJvmOptions extends JvmOptions<EsJvmOptions> {
+ private static final String ELASTICSEARCH_JVM_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
+ "# Please use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
+ "\n" +
+ "# DO NOT EDIT THIS FILE\n" +
+ "\n";
+
+ public EsJvmOptions() {
+ super(mandatoryOptions());
+ }
+
+ private static Map<String, String> mandatoryOptions() {
+ Map<String, String> res = new LinkedHashMap<>(16);
+ res.put("-XX:+UseConcMarkSweepGC", "");
+ res.put("-XX:CMSInitiatingOccupancyFraction=", "75");
+ res.put("-XX:+UseCMSInitiatingOccupancyOnly", "");
+ res.put("-XX:+AlwaysPreTouch", "");
+ res.put("-server", "");
+ res.put("-Xss", "1m");
+ res.put("-Djava.awt.headless=", "true");
+ res.put("-Dfile.encoding=", "UTF-8");
+ res.put("-Djna.nosys=", "true");
+ res.put("-Djdk.io.permissionsUseCanonicalPath=", "true");
+ res.put("-Dio.netty.noUnsafe=", "true");
+ res.put("-Dio.netty.noKeySetOptimization=", "true");
+ res.put("-Dio.netty.recycler.maxCapacityPerThread=", "0");
+ res.put("-Dlog4j.shutdownHookEnabled=", "false");
+ res.put("-Dlog4j2.disable.jmx=", "true");
+ res.put("-Dlog4j.skipJansi=", "true");
+ return res;
+ }
+
+ public void writeToJvmOptionFile(File file) {
+ String jvmOptions = getAll().stream().collect(Collectors.joining("\n"));
+ String jvmOptionsContent = ELASTICSEARCH_JVM_OPTIONS_HEADER + jvmOptions;
+ try {
+ Files.write(file.toPath(), jvmOptionsContent.getBytes(Charset.forName("UTF-8")));
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot write Elasticsearch jvm options file", e);
+ }
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.process.ProcessId;
+import org.sonar.process.System2;
+
+public class JavaCommand<T extends JvmOptions> extends AbstractCommand<JavaCommand<T>> {
+ // entry point
+ private String className;
+ private JvmOptions<T> jvmOptions;
+ // relative path to JAR files
+ private final List<String> classpath = new ArrayList<>();
+
+ public JavaCommand(ProcessId id, File workDir) {
+ super(id, workDir, System2.INSTANCE);
+ }
+
+ public JvmOptions<T> getJvmOptions() {
+ return jvmOptions;
+ }
+
+ public JavaCommand<T> setJvmOptions(JvmOptions<T> jvmOptions) {
+ this.jvmOptions = jvmOptions;
+
+ return this;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public JavaCommand<T> setClassName(String className) {
+ this.className = className;
+ return this;
+ }
+
+ public List<String> getClasspath() {
+ return classpath;
+ }
+
+ public JavaCommand<T> addClasspath(String s) {
+ classpath.add(s);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaCommand{" + "workDir=" + getWorkDir() +
+ ", jvmOptions=" + jvmOptions +
+ ", className='" + className + '\'' +
+ ", classpath=" + classpath +
+ ", arguments=" + getArguments() +
+ ", envVariables=" + getEnvVariables() +
+ ", suppressedEnvVariables=" + getSuppressedEnvVariables() +
+ '}';
+ }
+}
--- /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.command;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.process.MessageException;
+import org.sonar.process.Props;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+
+public class JvmOptions<T extends JvmOptions> {
+ private static final String JVM_OPTION_NOT_NULL_ERROR_MESSAGE = "a JVM option can't be null";
+
+ private final HashMap<String, String> mandatoryOptions = new HashMap<>();
+ private final LinkedHashSet<String> options = new LinkedHashSet<>();
+
+ public JvmOptions() {
+ this(Collections.emptyMap());
+ }
+
+ public JvmOptions(Map<String, String> mandatoryJvmOptions) {
+ requireNonNull(mandatoryJvmOptions, JVM_OPTION_NOT_NULL_ERROR_MESSAGE)
+ .entrySet()
+ .stream()
+ .filter(e -> {
+ requireNonNull(e.getKey(), "JVM option prefix can't be null");
+ if (e.getKey().trim().isEmpty()) {
+ throw new IllegalArgumentException("JVM option prefix can't be empty");
+ }
+ requireNonNull(e.getValue(), "JVM option value can't be null");
+ return true;
+ }).forEach(e -> {
+ String key = e.getKey().trim();
+ String value = e.getValue().trim();
+ mandatoryOptions.put(key, value);
+ add(key + value);
+ });
+ }
+
+ public T addFromMandatoryProperty(Props props, String propertyName) {
+ String value = props.nonNullValue(propertyName);
+ if (!value.isEmpty()) {
+ List<String> jvmOptions = Arrays.stream(value.split(" (?=-)")).map(String::trim).collect(Collectors.toList());
+ checkOptionFormat(propertyName, jvmOptions);
+ checkMandatoryOptionOverwrite(propertyName, jvmOptions);
+ options.addAll(jvmOptions);
+ }
+
+ return castThis();
+ }
+
+ private static void checkOptionFormat(String propertyName, List<String> jvmOptionsFromProperty) {
+ List<String> invalidOptions = jvmOptionsFromProperty.stream()
+ .filter(JvmOptions::isInvalidOption)
+ .collect(Collectors.toList());
+ if (!invalidOptions.isEmpty()) {
+ throw new MessageException(format(
+ "a JVM option can't be empty and must start with '-'. The following JVM options defined by property '%s' are invalid: %s",
+ propertyName,
+ invalidOptions.stream()
+ .collect(joining(", "))));
+ }
+ }
+
+ private void checkMandatoryOptionOverwrite(String propertyName, List<String> jvmOptionsFromProperty) {
+ List<Match> matches = jvmOptionsFromProperty.stream()
+ .map(jvmOption -> new Match(jvmOption, mandatoryOptionFor(jvmOption)))
+ .filter(match -> match.getMandatoryOption() != null)
+ .collect(Collectors.toList());
+ if (!matches.isEmpty()) {
+ throw new MessageException(format(
+ "a JVM option can't overwrite mandatory JVM options. The following JVM options defined by property '%s' are invalid: %s",
+ propertyName,
+ matches.stream()
+ .map(m -> m.getOption() + " overwrites " + m.mandatoryOption.getKey() + m.mandatoryOption.getValue())
+ .collect(joining(", "))));
+ }
+ }
+
+ /**
+ * Add an option.
+ * Argument is trimmed before being added.
+ *
+ * @throws IllegalArgumentException if argument is empty or does not start with {@code -}.
+ */
+ public T add(String str) {
+ requireNonNull(str, JVM_OPTION_NOT_NULL_ERROR_MESSAGE);
+ String value = str.trim();
+ if (isInvalidOption(value)) {
+ throw new IllegalArgumentException("a JVM option can't be empty and must start with '-'");
+ }
+ checkMandatoryOptionOverwrite(value);
+ options.add(value);
+
+ return castThis();
+ }
+
+ private void checkMandatoryOptionOverwrite(String value) {
+ Map.Entry<String, String> overriddenMandatoryOption = mandatoryOptionFor(value);
+ if (overriddenMandatoryOption != null) {
+ throw new MessageException(String.format(
+ "a JVM option can't overwrite mandatory JVM options. %s overwrites %s",
+ value,
+ overriddenMandatoryOption.getKey() + overriddenMandatoryOption.getValue()));
+ }
+ }
+
+ @CheckForNull
+ private Map.Entry<String, String> mandatoryOptionFor(String jvmOption) {
+ return mandatoryOptions.entrySet().stream()
+ .filter(s -> jvmOption.startsWith(s.getKey()) && !jvmOption.equals(s.getKey() + s.getValue()))
+ .findFirst()
+ .orElse(null);
+ }
+
+ private static boolean isInvalidOption(String value) {
+ return value.isEmpty() || !value.startsWith("-");
+ }
+
+ @SuppressWarnings("unchecked")
+ private T castThis() {
+ return (T) this;
+ }
+
+ public List<String> getAll() {
+ return new ArrayList<>(options);
+ }
+
+ @Override
+ public String toString() {
+ return options.toString();
+ }
+
+ private static final class Match {
+ private final String option;
+
+ private final Map.Entry<String, String> mandatoryOption;
+
+ private Match(String option, @Nullable Map.Entry<String, String> mandatoryOption) {
+ this.option = option;
+ this.mandatoryOption = mandatoryOption;
+ }
+
+ String getOption() {
+ return option;
+ }
+
+ @CheckForNull
+ Map.Entry<String, String> getMandatoryOption() {
+ return mandatoryOption;
+ }
+
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class WebJvmOptions extends JvmOptions<WebJvmOptions> {
+ public WebJvmOptions(File tmpDir) {
+ super(mandatoryOptions(tmpDir));
+ }
+
+ private static Map<String, String> mandatoryOptions(File tmpDir) {
+ Map<String, String> res = new LinkedHashMap<>(3);
+ res.put("-Djava.awt.headless=", "true");
+ res.put("-Dfile.encoding=", "UTF-8");
+ res.put("-Djava.io.tmpdir=", tmpDir.getAbsolutePath());
+ return res;
+ }
+}
--- /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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.application.command;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /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.es;
+
+import java.io.File;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+/**
+ * Holds {@link File} to the various directories of ElasticSearch distribution embedded in SonarQube and provides
+ * {@link File} objects to the various files of it SonarQube cares about.
+ *
+ * <p>
+ * This class does not ensure files nor directories actually exist.
+ * </p>
+ */
+public class EsFileSystem {
+ private final File homeDirectory;
+ private final File dataDirectory;
+ private final File confDirectory;
+ private final File logDirectory;
+
+ public EsFileSystem(Props props) {
+ File sqHomeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
+
+ this.homeDirectory = new File(sqHomeDir, "elasticsearch");
+ this.dataDirectory = buildDataDir(props, sqHomeDir);
+ this.confDirectory = buildConfDir(props);
+ this.logDirectory = buildLogPath(props);
+ }
+
+ private static File buildDataDir(Props props, File sqHomeDir) {
+ String dataPath = props.value(ProcessProperties.PATH_DATA);
+ if (StringUtils.isNotEmpty(dataPath)) {
+ return new File(dataPath, "es");
+ }
+ return new File(sqHomeDir, "data/es");
+ }
+
+ private static File buildLogPath(Props props) {
+ return props.nonNullValueAsFile(ProcessProperties.PATH_LOGS);
+ }
+
+ private static File buildConfDir(Props props) {
+ File tempPath = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP);
+ return new File(new File(tempPath, "conf"), "es");
+ }
+
+ public File getHomeDirectory() {
+ return homeDirectory;
+ }
+
+ public File getDataDirectory() {
+ return dataDirectory;
+ }
+
+ public File getConfDirectory() {
+ return confDirectory;
+ }
+
+ public File getLogDirectory() {
+ return logDirectory;
+ }
+
+ public File getExecutable() {
+ return new File(homeDirectory, "bin/" + getExecutableName());
+ }
+
+ private static String getExecutableName() {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ return "elasticsearch.bat";
+ }
+ return "elasticsearch";
+ }
+
+ public File getLog4j2Properties() {
+ return new File(confDirectory, "log4j2.properties");
+ }
+
+ public File getElasticsearchYml() {
+ return new File(confDirectory, "elasticsearch.yml");
+ }
+
+ public File getJvmOptions() {
+ return new File(confDirectory, "jvm.options");
+ }
+}
--- /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.es;
+
+import ch.qos.logback.classic.Level;
+import java.io.File;
+import java.util.Properties;
+import org.sonar.process.ProcessId;
+import org.sonar.process.Props;
+import org.sonar.process.logging.Log4JPropertiesBuilder;
+import org.sonar.process.logging.LogLevelConfig;
+import org.sonar.process.logging.RootLoggerConfig;
+
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
+
+public class EsLogging {
+
+ public Properties createProperties(Props props, File logDir) {
+ Log4JPropertiesBuilder log4JPropertiesBuilder = new Log4JPropertiesBuilder(props);
+ RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build();
+ String logPattern = log4JPropertiesBuilder.buildLogPattern(config);
+
+ log4JPropertiesBuilder.internalLogLevel(Level.ERROR);
+ log4JPropertiesBuilder.configureGlobalFileLog(config, logDir, logPattern);
+ log4JPropertiesBuilder.apply(
+ LogLevelConfig.newBuilder(log4JPropertiesBuilder.getRootLoggerName())
+ .rootLevelFor(ProcessId.ELASTICSEARCH)
+ .build());
+
+ return log4JPropertiesBuilder.get();
+ }
+
+}
--- /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.es;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+import org.sonar.process.System2;
+
+import static java.lang.String.valueOf;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NAME;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_NAME;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+
+public class EsSettings {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(EsSettings.class);
+ private static final String STANDALONE_NODE_NAME = "sonarqube";
+
+ private final Props props;
+ private final EsFileSystem fileSystem;
+
+ private final boolean clusterEnabled;
+ private final String clusterName;
+ private final String nodeName;
+
+ public EsSettings(Props props, EsFileSystem fileSystem, System2 system2) {
+ this.props = props;
+ this.fileSystem = fileSystem;
+
+ this.clusterName = props.nonNullValue(CLUSTER_NAME);
+ this.clusterEnabled = props.valueAsBoolean(CLUSTER_ENABLED);
+ if (this.clusterEnabled) {
+ this.nodeName = props.value(CLUSTER_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString());
+ } else {
+ this.nodeName = STANDALONE_NODE_NAME;
+ }
+ String esJvmOptions = system2.getenv("ES_JVM_OPTIONS");
+ if (esJvmOptions != null && !esJvmOptions.trim().isEmpty()) {
+ LOGGER.warn("ES_JVM_OPTIONS is defined but will be ignored. " +
+ "Use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch");
+ }
+ }
+
+ public Map<String, String> build() {
+ Map<String, String> builder = new HashMap<>();
+ configureFileSystem(builder);
+ configureNetwork(builder);
+ configureCluster(builder);
+ configureAction(builder);
+ return builder;
+ }
+
+ private void configureFileSystem(Map<String, String> builder) {
+ builder.put("path.data", fileSystem.getDataDirectory().getAbsolutePath());
+ builder.put("path.conf", fileSystem.getConfDirectory().getAbsolutePath());
+ builder.put("path.logs", fileSystem.getLogDirectory().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);
+ }
+ }
+
+ 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(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 static void configureAction(Map<String, String> builder) {
+ builder.put("action.auto_create_index", String.valueOf(false));
+ }
+}
--- /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.es;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.Map;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+
+import static org.yaml.snakeyaml.DumperOptions.FlowStyle.BLOCK;
+
+public class EsYmlSettings {
+ private static final String ELASTICSEARCH_YML_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
+ "\n" +
+ "# DO NOT EDIT THIS FILE\n" +
+ "\n";
+
+ private final Map<String, String> elasticsearchSettings;
+
+ public EsYmlSettings(Map<String, String> elasticsearchSettings) {
+ this.elasticsearchSettings = elasticsearchSettings;
+ }
+
+ public void writeToYmlSettingsFile(File file) {
+ DumperOptions dumperOptions = new DumperOptions();
+ dumperOptions.setPrettyFlow(true);
+ dumperOptions.setDefaultFlowStyle(BLOCK);
+ Yaml yaml = new Yaml(dumperOptions);
+ String output = ELASTICSEARCH_YML_OPTIONS_HEADER + yaml.dump(elasticsearchSettings);
+ try {
+ Files.write(file.toPath(), output.getBytes(Charset.forName("UTF-8")));
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot write Elasticsearch yml settings file", e);
+ }
+ }
+}
--- /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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.application.es;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import org.elasticsearch.transport.Netty4Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.sonar.process.command.EsCommand;
+import org.sonar.application.command.EsCommand;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
package org.sonar.application.process;
import java.io.Closeable;
-import org.sonar.process.command.EsCommand;
-import org.sonar.process.command.JavaCommand;
+import org.sonar.application.command.EsCommand;
+import org.sonar.application.command.JavaCommand;
public interface ProcessLauncher extends Closeable {
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sonar.application.command.AbstractCommand;
+import org.sonar.application.command.EsCommand;
+import org.sonar.application.command.JavaCommand;
+import org.sonar.application.command.JvmOptions;
+import org.sonar.application.es.EsFileSystem;
import org.sonar.process.ProcessId;
-import org.sonar.process.command.AbstractCommand;
-import org.sonar.process.command.EsCommand;
-import org.sonar.process.command.JavaCommand;
-import org.sonar.process.es.EsFileSystem;
-import org.sonar.process.jmvoptions.JvmOptions;
import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
import org.sonar.process.sharedmemoryfile.ProcessCommands;
import org.sonar.application.process.ProcessMonitor;
import org.sonar.cluster.localclient.HazelcastClient;
import org.sonar.process.ProcessId;
-import org.sonar.process.command.AbstractCommand;
-import org.sonar.process.command.CommandFactory;
-import org.sonar.process.command.EsCommand;
-import org.sonar.process.command.JavaCommand;
+import org.sonar.application.command.AbstractCommand;
+import org.sonar.application.command.CommandFactory;
+import org.sonar.application.command.EsCommand;
+import org.sonar.application.command.JavaCommand;
import static java.util.Collections.synchronizedList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
--- /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.command;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.sonar.process.ProcessId;
+import org.sonar.process.System2;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+public class AbstractCommandTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_throws_NPE_of_ProcessId_is_null() throws IOException {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("ProcessId can't be null");
+
+ new AbstractCommand<AbstractCommand>(null, temp.newFolder(), System2.INSTANCE) {
+
+ };
+ }
+
+ @Test
+ public void constructor_throws_NPE_of_workDir_is_null() throws IOException {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("workDir can't be null");
+
+ new AbstractCommand<AbstractCommand>(ProcessId.WEB_SERVER, null, System2.INSTANCE) {
+
+ };
+ }
+
+ @Test
+ public void test_command_with_complete_information() throws Exception {
+ File workDir = temp.newFolder();
+ AbstractCommand command = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, System2.INSTANCE) {
+
+ };
+
+ command.setArgument("first_arg", "val1");
+ Properties args = new Properties();
+ args.setProperty("second_arg", "val2");
+ command.setArguments(args);
+
+ command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
+
+ assertThat(command.toString()).isNotNull();
+ assertThat(command.getWorkDir()).isSameAs(workDir);
+
+ // copy current env variables
+ assertThat(command.getEnvVariables().get("JAVA_COMMAND_TEST")).isEqualTo("1000");
+ assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1);
+ }
+
+ @Test
+ public void setEnvVariable_fails_with_NPE_if_key_is_null() throws IOException {
+ File workDir = temp.newFolder();
+ AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, System2.INSTANCE) {
+
+ };
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("key can't be null");
+
+ underTest.setEnvVariable(null, randomAlphanumeric(30));
+ }
+
+ @Test
+ public void setEnvVariable_fails_with_NPE_if_value_is_null() throws IOException {
+ File workDir = temp.newFolder();
+ AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, System2.INSTANCE) {
+
+ };
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("value can't be null");
+
+ underTest.setEnvVariable(randomAlphanumeric(30), null);
+ }
+
+ @Test
+ public void constructor_puts_System_getEnv_into_map_of_env_variables() throws IOException {
+ File workDir = temp.newFolder();
+ System2 system2 = Mockito.mock(System2.class);
+ Map<String, String> env = IntStream.range(0, 1 + new Random().nextInt(99)).mapToObj(String::valueOf).collect(Collectors.toMap(i -> "key" + i, j -> "value" + j));
+ when(system2.getenv()).thenReturn(env);
+ AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, system2) {
+
+ };
+
+ assertThat(underTest.getEnvVariables()).isEqualTo(env);
+ }
+
+ @Test
+ public void suppressEnvVariable_remove_existing_env_variable_and_add_variable_to_set_of_suppressed_variables() throws IOException {
+ File workDir = temp.newFolder();
+ System2 system2 = Mockito.mock(System2.class);
+ Map<String, String> env = new HashMap<>();
+ String key1 = randomAlphanumeric(3);
+ env.put(key1, randomAlphanumeric(9));
+ when(system2.getenv()).thenReturn(env);
+ AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, system2) {
+
+ };
+
+ underTest.suppressEnvVariable(key1);
+
+ assertThat(underTest.getEnvVariables()).doesNotContainKey(key1);
+ assertThat(underTest.getSuppressedEnvVariables()).containsOnly(key1);
+ }
+
+}
--- /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.command;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CeJvmOptionsTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void constructor_sets_mandatory_JVM_options() throws IOException {
+ File tmpDir = temporaryFolder.newFolder();
+ CeJvmOptions underTest = new CeJvmOptions(tmpDir);
+
+ assertThat(underTest.getAll()).containsExactly(
+ "-Djava.awt.headless=true", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=" + tmpDir.getAbsolutePath());
+ }
+}
--- /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.command;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+import org.sonar.process.System2;
+import org.sonar.application.logging.ListAppender;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+public class CommandFactoryImplTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private File homeDir;
+ private File tempDir;
+ private File logsDir;
+ private ListAppender listAppender;
+
+ @Before
+ public void setUp() throws Exception {
+ homeDir = temp.newFolder();
+ tempDir = temp.newFolder();
+ logsDir = temp.newFolder();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (listAppender != null) {
+ ListAppender.detachMemoryAppenderToLoggerOf(CommandFactoryImpl.class, listAppender);
+ }
+ }
+
+ @Test
+ public void constructor_logs_no_warning_if_env_variable_JAVA_TOOL_OPTIONS_is_not_set() {
+ System2 system2 = Mockito.mock(System2.class);
+ when(system2.getenv(anyString())).thenReturn(null);
+ attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
+
+ new CommandFactoryImpl(new Props(new Properties()), tempDir, system2);
+
+ assertThat(listAppender.getLogs()).isEmpty();
+ }
+
+ @Test
+ public void constructor_logs_warning_if_env_variable_JAVA_TOOL_OPTIONS_is_set() {
+ System2 system2 = Mockito.mock(System2.class);
+ when(system2.getenv("JAVA_TOOL_OPTIONS")).thenReturn("sds");
+ attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
+
+ new CommandFactoryImpl(new Props(new Properties()), tempDir, system2);
+
+ assertThat(listAppender.getLogs())
+ .extracting(ILoggingEvent::getMessage)
+ .containsOnly(
+ "JAVA_TOOL_OPTIONS is defined but will be ignored. " +
+ "Use properties sonar.*.javaOpts and/or sonar.*.javaAdditionalOpts in sonar.properties to change SQ JVM processes options");
+ }
+
+ @Test
+ public void createEsCommand_throws_ISE_if_es_binary_is_not_found() throws Exception {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Cannot find elasticsearch binary");
+
+ newFactory(new Properties()).createEsCommand();
+ }
+
+ @Test
+ public void createEsCommand_returns_command_for_default_settings() throws Exception {
+ prepareEsFileSystem();
+
+ Properties props = new Properties();
+ props.setProperty("sonar.search.host", "localhost");
+
+ EsCommand esCommand = newFactory(props).createEsCommand();
+
+ assertThat(esCommand.getClusterName()).isEqualTo("sonarqube");
+ assertThat(esCommand.getHost()).isNotEmpty();
+ assertThat(esCommand.getPort()).isEqualTo(9001);
+ assertThat(esCommand.getEsJvmOptions().getAll())
+ // enforced values
+ .contains("-XX:+UseConcMarkSweepGC", "-server", "-Dfile.encoding=UTF-8")
+ // default settings
+ .contains("-Xms512m", "-Xmx512m", "-XX:+HeapDumpOnOutOfMemoryError");
+ File esConfDir = new File(tempDir, "conf/es");
+ assertThat(esCommand.getEsOptions()).containsOnly("-Epath.conf=" + esConfDir.getAbsolutePath());
+ assertThat(esCommand.getEnvVariables())
+ .contains(entry("ES_JVM_OPTIONS", new File(esConfDir, "jvm.options").getAbsolutePath()))
+ .containsKey("JAVA_HOME");
+ assertThat(esCommand.getEsYmlSettings()).isNotNull();
+
+ assertThat(esCommand.getLog4j2Properties())
+ .contains(entry("appender.file_es.fileName", new File(logsDir, "es.log").getAbsolutePath()));
+
+ assertThat(esCommand.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
+ }
+
+ @Test
+ public void createEsCommand_returns_command_for_overridden_settings() throws Exception {
+ prepareEsFileSystem();
+
+ Properties props = new Properties();
+ props.setProperty("sonar.search.host", "localhost");
+ props.setProperty("sonar.cluster.name", "foo");
+ props.setProperty("sonar.search.port", "1234");
+ props.setProperty("sonar.search.javaOpts", "-Xms10G -Xmx10G");
+
+ EsCommand command = newFactory(props).createEsCommand();
+
+ assertThat(command.getClusterName()).isEqualTo("foo");
+ assertThat(command.getPort()).isEqualTo(1234);
+ assertThat(command.getEsJvmOptions().getAll())
+ // enforced values
+ .contains("-XX:+UseConcMarkSweepGC", "-server", "-Dfile.encoding=UTF-8")
+ // user settings
+ .contains("-Xms10G", "-Xmx10G")
+ // default values disabled
+ .doesNotContain("-XX:+HeapDumpOnOutOfMemoryError");
+ }
+
+ @Test
+ public void createWebCommand_returns_command_for_default_settings() throws Exception {
+ JavaCommand command = newFactory(new Properties()).createWebCommand(true);
+
+ assertThat(command.getClassName()).isEqualTo("org.sonar.server.app.WebServer");
+ assertThat(command.getWorkDir().getAbsolutePath()).isEqualTo(homeDir.getAbsolutePath());
+ assertThat(command.getClasspath())
+ .containsExactlyInAnyOrder("./lib/common/*", "./lib/server/*");
+ assertThat(command.getJvmOptions().getAll())
+ // enforced values
+ .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
+ // default settings
+ .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
+ .contains("-Xmx512m", "-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
+ assertThat(command.getProcessId()).isEqualTo(ProcessId.WEB_SERVER);
+ assertThat(command.getEnvVariables())
+ .containsKey("JAVA_HOME");
+ assertThat(command.getArguments())
+ // default settings
+ .contains(entry("sonar.web.javaOpts", "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError"))
+ .contains(entry("sonar.cluster.enabled", "false"));
+
+ assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
+ }
+
+ @Test
+ public void createWebCommand_configures_command_with_overridden_settings() throws Exception {
+ Properties props = new Properties();
+ props.setProperty("sonar.web.port", "1234");
+ props.setProperty("sonar.web.javaOpts", "-Xmx10G");
+ JavaCommand command = newFactory(props).createWebCommand(true);
+
+ assertThat(command.getJvmOptions().getAll())
+ // enforced values
+ .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
+ // default settings
+ .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
+ // overridden values
+ .contains("-Xmx10G")
+ .doesNotContain("-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
+ assertThat(command.getArguments())
+ // default settings
+ .contains(entry("sonar.web.javaOpts", "-Xmx10G"))
+ .contains(entry("sonar.cluster.enabled", "false"));
+
+ assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
+ }
+
+ @Test
+ public void createWebCommand_adds_configured_jdbc_driver_to_classpath() throws Exception {
+ Properties props = new Properties();
+ File driverFile = temp.newFile();
+ props.setProperty("sonar.jdbc.driverPath", driverFile.getAbsolutePath());
+
+ JavaCommand command = newFactory(props).createWebCommand(true);
+
+ assertThat(command.getClasspath())
+ .containsExactlyInAnyOrder("./lib/common/*", "./lib/server/*", driverFile.getAbsolutePath());
+ }
+
+ private void prepareEsFileSystem() throws IOException {
+ FileUtils.touch(new File(homeDir, "elasticsearch/bin/elasticsearch"));
+ FileUtils.touch(new File(homeDir, "elasticsearch/bin/elasticsearch.bat"));
+ }
+
+ private CommandFactory newFactory(Properties userProps) throws IOException {
+ Properties p = new Properties();
+ p.setProperty("sonar.path.home", homeDir.getAbsolutePath());
+ p.setProperty("sonar.path.temp", tempDir.getAbsolutePath());
+ p.setProperty("sonar.path.logs", logsDir.getAbsolutePath());
+ p.putAll(userProps);
+
+ Props props = new Props(p);
+ ProcessProperties.completeDefaults(props);
+ return new CommandFactoryImpl(props, tempDir, System2.INSTANCE);
+ }
+
+ private <T> void attachMemoryAppenderToLoggerOf(Class<T> loggerClass) {
+ this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(loggerClass);
+ }
+
+}
--- /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.command;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.test.ExceptionCauseMatcher;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsJvmOptionsTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_sets_mandatory_JVM_options() {
+ EsJvmOptions underTest = new EsJvmOptions();
+
+ assertThat(underTest.getAll()).containsExactly(
+ "-XX:+UseConcMarkSweepGC",
+ "-XX:CMSInitiatingOccupancyFraction=75",
+ "-XX:+UseCMSInitiatingOccupancyOnly",
+ "-XX:+AlwaysPreTouch",
+ "-server",
+ "-Xss1m",
+ "-Djava.awt.headless=true",
+ "-Dfile.encoding=UTF-8",
+ "-Djna.nosys=true",
+ "-Djdk.io.permissionsUseCanonicalPath=true",
+ "-Dio.netty.noUnsafe=true",
+ "-Dio.netty.noKeySetOptimization=true",
+ "-Dio.netty.recycler.maxCapacityPerThread=0",
+ "-Dlog4j.shutdownHookEnabled=false",
+ "-Dlog4j2.disable.jmx=true",
+ "-Dlog4j.skipJansi=true");
+ }
+
+ @Test
+ public void writeToJvmOptionFile_writes_all_JVM_options_to_file_with_warning_header() throws IOException {
+ File file = temporaryFolder.newFile();
+ EsJvmOptions underTest = new EsJvmOptions()
+ .add("-foo")
+ .add("-bar");
+
+ underTest.writeToJvmOptionFile(file);
+
+ assertThat(file).hasContent(
+ "# This file has been automatically generated by SonarQube during startup.\n" +
+ "# Please use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
+ "\n" +
+ "# DO NOT EDIT THIS FILE\n" +
+ "\n" +
+ "-XX:+UseConcMarkSweepGC\n" +
+ "-XX:CMSInitiatingOccupancyFraction=75\n" +
+ "-XX:+UseCMSInitiatingOccupancyOnly\n" +
+ "-XX:+AlwaysPreTouch\n" +
+ "-server\n" +
+ "-Xss1m\n" +
+ "-Djava.awt.headless=true\n" +
+ "-Dfile.encoding=UTF-8\n" +
+ "-Djna.nosys=true\n" +
+ "-Djdk.io.permissionsUseCanonicalPath=true\n" +
+ "-Dio.netty.noUnsafe=true\n" +
+ "-Dio.netty.noKeySetOptimization=true\n" +
+ "-Dio.netty.recycler.maxCapacityPerThread=0\n" +
+ "-Dlog4j.shutdownHookEnabled=false\n" +
+ "-Dlog4j2.disable.jmx=true\n" +
+ "-Dlog4j.skipJansi=true\n" +
+ "-foo\n" +
+ "-bar");
+
+ }
+
+ @Test
+ public void writeToJvmOptionFile_throws_ISE_in_case_of_IOException() throws IOException {
+ File notAFile = temporaryFolder.newFolder();
+ EsJvmOptions underTest = new EsJvmOptions();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Cannot write Elasticsearch jvm options file");
+ expectedException.expectCause(ExceptionCauseMatcher.hasType(IOException.class));
+
+ underTest.writeToJvmOptionFile(notAFile);
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.util.Properties;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JavaCommandTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void test_command_with_complete_information() throws Exception {
+ File workDir = temp.newFolder();
+ JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, workDir);
+
+ command.setArgument("first_arg", "val1");
+ Properties args = new Properties();
+ args.setProperty("second_arg", "val2");
+ command.setArguments(args);
+
+ command.setClassName("org.sonar.ElasticSearch");
+ command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
+ command.addClasspath("lib/*.jar");
+ command.addClasspath("conf/*.xml");
+ JvmOptions<JvmOptions> jvmOptions = new JvmOptions<JvmOptions>() {};
+ command.setJvmOptions(jvmOptions);
+
+ assertThat(command.toString()).isNotNull();
+ assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml");
+ assertThat(command.getJvmOptions()).isSameAs(jvmOptions);
+ assertThat(command.getWorkDir()).isSameAs(workDir);
+ assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch");
+
+ // copy current env variables
+ assertThat(command.getEnvVariables().get("JAVA_COMMAND_TEST")).isEqualTo("1000");
+ assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1);
+ }
+
+}
--- /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.command;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.process.MessageException;
+import org.sonar.process.Props;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+@RunWith(DataProviderRunner.class)
+public class JvmOptionsTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final Random random = new Random();
+ private final String randomPropertyName = randomAlphanumeric(3);
+ private final String randomPrefix = "-" + randomAlphabetic(5).toLowerCase(Locale.ENGLISH);
+ private final String randomValue = randomAlphanumeric(4).toLowerCase(Locale.ENGLISH);
+ private final Properties properties = new Properties();
+ private final JvmOptions underTest = new JvmOptions();
+
+ @Test
+ public void constructor_without_arguments_creates_empty_JvmOptions() {
+ JvmOptions<JvmOptions> testJvmOptions = new JvmOptions<>();
+
+ assertThat(testJvmOptions.getAll()).isEmpty();
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_argument_is_null() {
+ expectJvmOptionNotNullNPE();
+
+ new JvmOptions(null);
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_any_option_prefix_is_null() {
+ Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
+ Stream.of(
+ IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
+ Stream.of(new Option(null, "value")))
+ .flatMap(s -> s));
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("JVM option prefix can't be null");
+
+ new JvmOptions(mandatoryJvmOptions);
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void constructor_throws_IAE_if_any_option_prefix_is_empty(String emptyString) {
+ Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
+ Stream.of(
+ IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
+ Stream.of(new Option(emptyString, "value")))
+ .flatMap(s -> s));
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("JVM option prefix can't be empty");
+
+ new JvmOptions(mandatoryJvmOptions);
+ }
+
+ @Test
+ public void constructor_throws_IAE_if_any_option_prefix_does_not_start_with_dash() {
+ String invalidPrefix = randomAlphanumeric(3);
+ Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
+ Stream.of(
+ IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
+ Stream.of(new Option(invalidPrefix, "value")))
+ .flatMap(s -> s));
+
+ expectJvmOptionNotEmptyAndStartByDashIAE();
+
+ new JvmOptions(mandatoryJvmOptions);
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_any_option_value_is_null() {
+ Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
+ Stream.of(
+ IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
+ Stream.of(new Option("-prefix", null)))
+ .flatMap(s -> s));
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("JVM option value can't be null");
+
+ new JvmOptions(mandatoryJvmOptions);
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void constructor_accepts_any_empty_option_value(String emptyString) {
+ Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
+ Stream.of(
+ IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
+ Stream.of(new Option("-prefix", emptyString)))
+ .flatMap(s -> s));
+
+ new JvmOptions(mandatoryJvmOptions);
+ }
+
+ @Test
+ public void add_throws_NPE_if_argument_is_null() {
+ expectJvmOptionNotNullNPE();
+
+ underTest.add(null);
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void add_throws_IAE_if_argument_is_empty(String emptyString) {
+ expectJvmOptionNotEmptyAndStartByDashIAE();
+
+ underTest.add(emptyString);
+ }
+
+ @Test
+ public void add_throws_IAE_if_argument_does_not_start_with_dash() {
+ expectJvmOptionNotEmptyAndStartByDashIAE();
+
+ underTest.add(randomAlphanumeric(3));
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void add_adds_with_trimming(String emptyString) {
+ underTest.add(emptyString + "-foo" + emptyString);
+
+ assertThat(underTest.getAll()).containsOnly("-foo");
+ }
+
+ @Test
+ public void add_throws_MessageException_if_option_starts_with_prefix_of_mandatory_option_but_has_different_value() {
+ String[] optionOverrides = {
+ randomPrefix,
+ randomPrefix + randomAlphanumeric(1),
+ randomPrefix + randomAlphanumeric(2),
+ randomPrefix + randomAlphanumeric(3),
+ randomPrefix + randomAlphanumeric(4),
+ randomPrefix + randomValue.substring(1),
+ randomPrefix + randomValue.substring(2),
+ randomPrefix + randomValue.substring(3)
+ };
+
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ for (String optionOverride : optionOverrides) {
+ try {
+ underTest.add(optionOverride);
+ fail("an MessageException should have been thrown");
+ } catch (MessageException e) {
+ assertThat(e.getMessage()).isEqualTo("a JVM option can't overwrite mandatory JVM options. " + optionOverride + " overwrites " + randomPrefix + randomValue);
+ }
+ }
+ }
+
+ @Test
+ public void add_checks_against_mandatory_options_is_case_sensitive() {
+ String[] optionOverrides = {
+ randomPrefix,
+ randomPrefix + randomAlphanumeric(1),
+ randomPrefix + randomAlphanumeric(2),
+ randomPrefix + randomAlphanumeric(3),
+ randomPrefix + randomAlphanumeric(4),
+ randomPrefix + randomValue.substring(1),
+ randomPrefix + randomValue.substring(2),
+ randomPrefix + randomValue.substring(3)
+ };
+
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ for (String optionOverride : optionOverrides) {
+ underTest.add(optionOverride.toUpperCase(Locale.ENGLISH));
+ }
+ }
+
+ @Test
+ public void add_accepts_property_equal_to_mandatory_option_and_does_not_add_it_twice() {
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ underTest.add(randomPrefix + randomValue);
+
+ assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
+ }
+
+ @Test
+ public void addFromMandatoryProperty_fails_with_IAE_if_property_does_not_exist() {
+ expectMissingPropertyIAE(this.randomPropertyName);
+
+ underTest.addFromMandatoryProperty(new Props(properties), this.randomPropertyName);
+ }
+
+ @Test
+ public void addFromMandatoryProperty_fails_with_IAE_if_property_contains_an_empty_value() {
+ expectMissingPropertyIAE(this.randomPropertyName);
+
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void addFromMandatoryProperty_adds_single_option_of_property_with_trimming(String emptyString) {
+ properties.put(randomPropertyName, emptyString + "-foo" + emptyString);
+
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+
+ assertThat(underTest.getAll()).containsOnly("-foo");
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void addFromMandatoryProperty_fails_with_MessageException_if_property_does_not_start_with_dash_after_trimmed(String emptyString) {
+ properties.put(randomPropertyName, emptyString + "foo -bar");
+
+ expectJvmOptionNotEmptyAndStartByDashMessageException(randomPropertyName, "foo");
+
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+ }
+
+ @Test
+ @UseDataProvider("variousEmptyStrings")
+ public void addFromMandatoryProperty_adds_options_of_property_with_trimming(String emptyString) {
+ properties.put(randomPropertyName, emptyString + "-foo" + emptyString + " -bar" + emptyString + " -duck" + emptyString);
+
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+
+ assertThat(underTest.getAll()).containsOnly("-foo", "-bar", "-duck");
+ }
+
+ @Test
+ public void addFromMandatoryProperty_supports_spaces_inside_options() {
+ properties.put(randomPropertyName, "-foo bar -duck");
+
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+
+ assertThat(underTest.getAll()).containsOnly("-foo bar", "-duck");
+ }
+
+ @Test
+ public void addFromMandatoryProperty_throws_IAE_if_option_starts_with_prefix_of_mandatory_option_but_has_different_value() {
+ String[] optionOverrides = {
+ randomPrefix,
+ randomPrefix + randomValue.substring(1),
+ randomPrefix + randomValue.substring(1),
+ randomPrefix + randomValue.substring(2),
+ randomPrefix + randomValue.substring(3),
+ randomPrefix + randomValue.substring(3) + randomAlphanumeric(1),
+ randomPrefix + randomValue.substring(3) + randomAlphanumeric(2),
+ randomPrefix + randomValue.substring(3) + randomAlphanumeric(3),
+ randomPrefix + randomValue + randomAlphanumeric(1)
+ };
+
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ for (String optionOverride : optionOverrides) {
+ try {
+ properties.put(randomPropertyName, optionOverride);
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+ fail("an MessageException should have been thrown");
+ } catch (MessageException e) {
+ assertThat(e.getMessage())
+ .isEqualTo("a JVM option can't overwrite mandatory JVM options. " +
+ "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " + optionOverride + " overwrites " + randomPrefix + randomValue);
+ }
+ }
+ }
+
+ @Test
+ public void addFromMandatoryProperty_checks_against_mandatory_options_is_case_sensitive() {
+ String[] optionOverrides = {
+ randomPrefix,
+ randomPrefix + randomValue.substring(1),
+ randomPrefix + randomValue.substring(1),
+ randomPrefix + randomValue.substring(2),
+ randomPrefix + randomValue.substring(3),
+ randomPrefix + randomValue.substring(3) + randomAlphanumeric(1),
+ randomPrefix + randomValue.substring(3) + randomAlphanumeric(2),
+ randomPrefix + randomValue.substring(3) + randomAlphanumeric(3),
+ randomPrefix + randomValue + randomAlphanumeric(1)
+ };
+
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ for (String optionOverride : optionOverrides) {
+ properties.setProperty(randomPropertyName, optionOverride.toUpperCase(Locale.ENGLISH));
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+ }
+ }
+
+ @Test
+ public void addFromMandatoryProperty_reports_all_overriding_options_in_single_exception() {
+ String overriding1 = randomPrefix;
+ String overriding2 = randomPrefix + randomValue + randomAlphanumeric(1);
+ properties.setProperty(randomPropertyName, "-foo " + overriding1 + " -bar " + overriding2);
+
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("a JVM option can't overwrite mandatory JVM options. " +
+ "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " +
+ overriding1 + " overwrites " + randomPrefix + randomValue + ", " + overriding2 + " overwrites " + randomPrefix + randomValue);
+
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+ }
+
+ @Test
+ public void addFromMandatoryProperty_accepts_property_equal_to_mandatory_option_and_does_not_add_it_twice() {
+ JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
+
+ properties.put(randomPropertyName, randomPrefix + randomValue);
+ underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
+
+ assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
+ }
+
+ @Test
+ public void toString_prints_all_jvm_options() {
+ underTest.add("-foo").add("-bar");
+
+ assertThat(underTest.toString()).isEqualTo("[-foo, -bar]");
+ }
+
+ private void expectJvmOptionNotNullNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("a JVM option can't be null");
+ }
+
+ private void expectJvmOptionNotEmptyAndStartByDashIAE() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("a JVM option can't be empty and must start with '-'");
+ }
+
+ private void expectJvmOptionNotEmptyAndStartByDashMessageException(String randomPropertyName, String option) {
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("a JVM option can't be empty and must start with '-'. " +
+ "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " + option);
+ }
+
+ public void expectMissingPropertyIAE(String randomPropertyName) {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Missing property: " + randomPropertyName);
+ }
+
+ @DataProvider()
+ public static Object[][] variousEmptyStrings() {
+ return new Object[][] {
+ {""},
+ {" "},
+ {" "}
+ };
+ }
+
+ private static Map<String, String> shuffleThenToMap(Stream<Option> stream) {
+ List<Option> options = stream.collect(Collectors.toList());
+ Collections.shuffle(options);
+ Map<String, String> res = new HashMap<>(options.size());
+ for (Option option : options) {
+ res.put(option.getPrefix(), option.getValue());
+ }
+ return res;
+ }
+
+ private static final class Option {
+ private final String prefix;
+ private final String value;
+
+ private Option(String prefix, String value) {
+ this.prefix = prefix;
+ this.value = value;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + prefix + "-" + value + ']';
+ }
+ }
+}
--- /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.command;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WebJvmOptionsTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void constructor_sets_mandatory_JVM_options() throws IOException {
+ File tmpDir = temporaryFolder.newFolder();
+ WebJvmOptions underTest = new WebJvmOptions(tmpDir);
+
+ assertThat(underTest.getAll()).containsExactly(
+ "-Djava.awt.headless=true", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=" + tmpDir.getAbsolutePath());
+ }
+
+}
--- /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.es;
+
+import java.io.File;
+import java.io.IOException;
+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 EsFileSystemTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_fails_with_IAE_if_sq_home_property_is_not_defined() {
+ Props props = new Props(new Properties());
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Property sonar.path.home is not set");
+
+ new EsFileSystem(props);
+ }
+
+ @Test
+ public void constructor_fails_with_IAE_if_temp_dir_property_is_not_defined() throws IOException {
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Property sonar.path.temp is not set");
+
+ new EsFileSystem(props);
+ }
+
+ @Test
+ public void getHomeDirectory_is_elasticsearch_subdirectory_of_sq_home_directory() throws IOException {
+ File sqHomeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getHomeDirectory()).isEqualTo(new File(sqHomeDir, "elasticsearch"));
+ }
+
+ @Test
+ public void getDataDirectory_is_data_es_subdirectory_of_sq_home_directory_by_default() throws IOException {
+ File sqHomeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getDataDirectory()).isEqualTo(new File(sqHomeDir, "data/es"));
+ }
+
+ @Test
+ public void override_data_dir() throws Exception {
+ File sqHomeDir = temp.newFolder();
+ File tempDir = temp.newFolder();
+ File dataDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ props.set(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getDataDirectory()).isEqualTo(new File(dataDir, "es"));
+ }
+
+ @Test
+ public void getLogDirectory_is_configured_with_non_nullable_PATH_LOG_variable() throws IOException {
+ File sqHomeDir = temp.newFolder();
+ File logDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getLogDirectory()).isEqualTo(logDir);
+ }
+
+ @Test
+ public void conf_directory_is_conf_es_subdirectory_of_sq_temp_directory() throws IOException {
+ File tempDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getConfDirectory()).isEqualTo(new File(tempDir, "conf/es"));
+ }
+
+ @Test
+ public void getExecutable_resolve_executable_for_platform() throws IOException {
+ File sqHomeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ assertThat(underTest.getExecutable()).isEqualTo(new File(sqHomeDir, "elasticsearch/bin/elasticsearch.bat"));
+ } else {
+ assertThat(underTest.getExecutable()).isEqualTo(new File(sqHomeDir, "elasticsearch/bin/elasticsearch"));
+ }
+ }
+
+ @Test
+ public void getLog4j2Properties_is_in_es_conf_directory() throws IOException {
+ File tempDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getLog4j2Properties()).isEqualTo(new File(tempDir, "conf/es/log4j2.properties"));
+ }
+
+ @Test
+ public void getElasticsearchYml_is_in_es_conf_directory() throws IOException {
+ File tempDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getElasticsearchYml()).isEqualTo(new File(tempDir, "conf/es/elasticsearch.yml"));
+ }
+
+ @Test
+ public void getJvmOptions_is_in_es_conf_directory() throws IOException {
+ File tempDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+
+ EsFileSystem underTest = new EsFileSystem(props);
+
+ assertThat(underTest.getJvmOptions()).isEqualTo(new File(tempDir, "conf/es/jvm.options"));
+ }
+}
--- /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.es;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsLoggingTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private EsLogging underTest = new EsLogging();
+
+ @Test
+ public void createProperties_with_empty_props() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps(), logDir);
+
+ verifyProperties(properties,
+ "status", "ERROR",
+ "appender.file_es.type", "RollingFile",
+ "appender.file_es.name", "file_es",
+ "appender.file_es.filePattern", new File(logDir, "es.%d{yyyy-MM-dd}.log").getAbsolutePath(),
+ "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(),
+ "appender.file_es.layout.type", "PatternLayout",
+ "appender.file_es.layout.pattern", "%d{yyyy.MM.dd HH:mm:ss} %-5level es[][%logger{1.}] %msg%n",
+ "appender.file_es.policies.type", "Policies",
+ "appender.file_es.policies.time.type", "TimeBasedTriggeringPolicy",
+ "appender.file_es.policies.time.interval", "1",
+ "appender.file_es.policies.time.modulate", "true",
+ "appender.file_es.strategy.type", "DefaultRolloverStrategy",
+ "appender.file_es.strategy.fileIndex", "nomax",
+ "appender.file_es.strategy.action.type", "Delete",
+ "appender.file_es.strategy.action.basepath", logDir.getAbsolutePath(),
+ "appender.file_es.strategy.action.maxDepth", "1",
+ "appender.file_es.strategy.action.condition.type", "IfFileName",
+ "appender.file_es.strategy.action.condition.glob", "es*",
+ "appender.file_es.strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount",
+ "appender.file_es.strategy.action.condition.nested_condition.exceeds", "7",
+ "rootLogger.level", "INFO",
+ "rootLogger.appenderRef.file_es.ref", "file_es");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_INFO_if_no_property_is_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps(), logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("INFO");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_global_property_if_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps("sonar.log.level", "TRACE"), logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("TRACE");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_process_property_if_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps("sonar.log.level.es", "DEBUG"), logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("DEBUG");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_process_property_over_global_property_if_both_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(
+ newProps(
+ "sonar.log.level", "DEBUG",
+ "sonar.log.level.es", "TRACE"),
+ logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("TRACE");
+ }
+
+ private static Props newProps(String... propertyKeysAndValues) {
+ assertThat(propertyKeysAndValues.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
+ Properties properties = new Properties();
+ for (int i = 0; i < propertyKeysAndValues.length; i++) {
+ properties.put(propertyKeysAndValues[i++], propertyKeysAndValues[i]);
+ }
+ return new Props(properties);
+ }
+
+ private void verifyProperties(Properties properties, String... expectedPropertyKeysAndValuesOrdered) {
+ if (expectedPropertyKeysAndValuesOrdered.length == 0) {
+ assertThat(properties.size()).isEqualTo(0);
+ } else {
+ assertThat(expectedPropertyKeysAndValuesOrdered.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
+ Set<String> keys = new HashSet<>(expectedPropertyKeysAndValuesOrdered.length / 2 + 1);
+ keys.add("status");
+ for (int i = 0; i < expectedPropertyKeysAndValuesOrdered.length; i++) {
+ String key = expectedPropertyKeysAndValuesOrdered[i++];
+ String value = expectedPropertyKeysAndValuesOrdered[i];
+ assertThat(properties.get(key)).describedAs("Unexpected value for property " + key).isEqualTo(value);
+ keys.add(key);
+ }
+ assertThat(properties.keySet()).containsOnly(keys.toArray());
+ }
+ }
+}
--- /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.es;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.application.logging.ListAppender;
+import org.sonar.cluster.ClusterProperties;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+import org.sonar.process.System2;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NAME;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+
+public class EsSettingsTest {
+
+ private static final boolean CLUSTER_ENABLED = true;
+ private static final boolean CLUSTER_DISABLED = false;
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ private ListAppender listAppender;
+
+ @After
+ public void tearDown() throws Exception {
+ if (listAppender != null) {
+ ListAppender.detachMemoryAppenderToLoggerOf(EsSettings.class, listAppender);
+ }
+ }
+
+ @Test
+ public void constructor_does_not_logs_warning_if_env_variable_ES_JVM_OPTIONS_is_not_set() {
+ this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(EsSettings.class);
+ Props props = minimalProps();
+ System2 system2 = mock(System2.class);
+ new EsSettings(props, new EsFileSystem(props), system2);
+
+ assertThat(listAppender.getLogs()).isEmpty();
+ }
+
+ @Test
+ public void constructor_does_not_logs_warning_if_env_variable_ES_JVM_OPTIONS_is_set_and_empty() {
+ this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(EsSettings.class);
+ Props props = minimalProps();
+ System2 system2 = mock(System2.class);
+ when(system2.getenv("ES_JVM_OPTIONS")).thenReturn(" ");
+ new EsSettings(props, new EsFileSystem(props), system2);
+
+ assertThat(listAppender.getLogs()).isEmpty();
+ }
+
+ @Test
+ public void constructor_logs_warning_if_env_variable_ES_JVM_OPTIONS_is_set_and_non_empty() throws IOException {
+ this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(EsSettings.class);
+ Props props = minimalProps();
+ System2 system2 = mock(System2.class);
+ when(system2.getenv("ES_JVM_OPTIONS")).thenReturn(randomAlphanumeric(2));
+ new EsSettings(props, new EsFileSystem(props), system2);
+
+ assertThat(listAppender.getLogs())
+ .extracting(ILoggingEvent::getMessage)
+ .containsOnly("ES_JVM_OPTIONS is defined but will be ignored. " +
+ "Use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch");
+ }
+
+ private Props minimalProps() {
+ Props props = new Props(new Properties());
+ props.set(ProcessProperties.PATH_HOME, randomAlphanumeric(12));
+ props.set(ProcessProperties.PATH_TEMP, randomAlphanumeric(12));
+ props.set(ProcessProperties.PATH_LOGS, randomAlphanumeric(12));
+ props.set(CLUSTER_NAME, randomAlphanumeric(12));
+ return props;
+ }
+
+ @Test
+ public void test_default_settings_for_standalone_mode() 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+ props.set(CLUSTER_NAME, "sonarqube");
+
+ EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
+
+ 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")).isNull();
+ assertThat(generated.get("path.conf")).isNotNull();
+
+ // http is disabled for security reasons
+ assertThat(generated.get("http.enabled")).isEqualTo("false");
+
+ 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");
+
+ assertThat(generated.get("action.auto_create_index")).isEqualTo("false");
+ }
+
+ @Test
+ public void test_default_settings_for_cluster_mode() 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+ props.set(ClusterProperties.CLUSTER_NAME, "sonarqube-1");
+ props.set(ClusterProperties.CLUSTER_ENABLED, "true");
+ props.set(ClusterProperties.CLUSTER_NODE_NAME, "node-1");
+
+ EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
+
+ Map<String, String> generated = esSettings.build();
+ assertThat(generated.get("cluster.name")).isEqualTo("sonarqube-1");
+ assertThat(generated.get("node.name")).isEqualTo("node-1");
+ }
+
+ @Test
+ public void test_node_name_default_for_cluster_mode() throws Exception {
+ File homeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ClusterProperties.CLUSTER_NAME, "sonarqube");
+ props.set(ClusterProperties.CLUSTER_ENABLED, "true");
+ 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+ EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
+ Map<String, String> generated = esSettings.build();
+ assertThat(generated.get("node.name")).startsWith("sonarqube-");
+ }
+
+ @Test
+ public void test_node_name_default_for_standalone_mode() throws Exception {
+ File homeDir = temp.newFolder();
+ Props props = new Props(new Properties());
+ props.set(ClusterProperties.CLUSTER_NAME, "sonarqube");
+ props.set(ClusterProperties.CLUSTER_ENABLED, "false");
+ 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
+ props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
+ EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
+ Map<String, String> generated = esSettings.build();
+ assertThat(generated.get("node.name")).isEqualTo("sonarqube");
+ }
+
+ @Test
+ public void path_properties_are_values_from_EsFileSystem_argument() throws IOException {
+ EsFileSystem mockedEsFileSystem = mock(EsFileSystem.class);
+ when(mockedEsFileSystem.getHomeDirectory()).thenReturn(new File("/foo/home"));
+ when(mockedEsFileSystem.getConfDirectory()).thenReturn(new File("/foo/conf"));
+ when(mockedEsFileSystem.getLogDirectory()).thenReturn(new File("/foo/log"));
+ when(mockedEsFileSystem.getDataDirectory()).thenReturn(new File("/foo/data"));
+
+ EsSettings underTest = new EsSettings(minProps(new Random().nextBoolean()), mockedEsFileSystem, System2.INSTANCE);
+
+ Map<String, String> generated = underTest.build();
+ assertThat(generated.get("path.data")).isEqualTo("/foo/data");
+ assertThat(generated.get("path.logs")).isEqualTo("/foo/log");
+ assertThat(generated.get("path.conf")).isEqualTo("/foo/conf");
+ }
+
+ @Test
+ public void set_discovery_settings_if_cluster_is_enabled() throws Exception {
+ Props props = minProps(CLUSTER_ENABLED);
+ props.set(CLUSTER_SEARCH_HOSTS, "1.2.3.4:9000,1.2.3.5:8080");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
+
+ 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(CLUSTER_ENABLED);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "ꝱꝲꝳପ");
+
+ EsSettings underTest = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
+
+ 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(CLUSTER_ENABLED);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).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(CLUSTER_ENABLED);
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
+
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("10s");
+ }
+
+ @Test
+ public void in_standalone_initialTimeout_is_not_overridable() throws Exception {
+ Props props = minProps(CLUSTER_DISABLED);
+ props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
+
+ assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("30s");
+ }
+
+ @Test
+ public void in_standalone_minimumMasterNodes_is_not_overridable() throws Exception {
+ Props props = minProps(CLUSTER_DISABLED);
+ props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
+
+ assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("1");
+ }
+
+ @Test
+ public void enable_http_connector() throws Exception {
+ Props props = minProps(CLUSTER_DISABLED);
+ props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).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(CLUSTER_DISABLED);
+ props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
+ props.set(ProcessProperties.SEARCH_HOST, "127.0.0.2");
+ Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).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(ClusterProperties.CLUSTER_ENABLED, Boolean.toString(cluster));
+ return props;
+ }
+}
--- /dev/null
+package org.sonar.application.es;/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsYmlSettingsTest {
+
+ @Rule
+ public TemporaryFolder folder= new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void test_generation_of_file() throws IOException {
+ File yamlFile = folder.newFile();
+ new EsYmlSettings(new HashMap<>()).writeToYmlSettingsFile(yamlFile);
+
+ assertThat(yamlFile).exists();
+ assertThat(yamlFile).hasContent("# This file has been automatically generated by SonarQube during startup.\n" +
+ "\n" +
+ "# DO NOT EDIT THIS FILE\n" +
+ "\n" +
+ "{\n" +
+ " }");
+ }
+
+ @Test
+ public void if_file_is_not_writable_ISE_must_be_thrown() throws IOException {
+ File yamlFile = folder.newFile();
+ yamlFile.setReadOnly();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Cannot write Elasticsearch yml settings file");
+
+ new EsYmlSettings(new HashMap<>()).writeToYmlSettingsFile(yamlFile);
+ }
+}
--- /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.logging;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.process.logging.LogbackHelper;
+
+public final class ListAppender extends AppenderBase<ILoggingEvent> {
+ private final List<ILoggingEvent> logs = new ArrayList<>();
+
+ @Override
+ protected void append(ILoggingEvent eventObject) {
+ logs.add(eventObject);
+ }
+
+ public List<ILoggingEvent> getLogs() {
+ return logs;
+ }
+
+ public static <T> ListAppender attachMemoryAppenderToLoggerOf(Class<T> loggerClass) {
+ ListAppender listAppender = new ListAppender();
+ new LogbackHelper().getRootContext().getLogger(loggerClass)
+ .addAppender(listAppender);
+ listAppender.start();
+ return listAppender;
+ }
+
+ public static <T> void detachMemoryAppenderToLoggerOf(Class<T> loggerClass, ListAppender listAppender) {
+ listAppender.stop();
+ new LogbackHelper().getRootContext().getLogger(loggerClass)
+ .detachAppender(listAppender);
+ }
+}
import org.junit.Test;
import org.slf4j.LoggerFactory;
import org.sonar.process.ProcessId;
-import org.sonar.process.command.EsCommand;
+import org.sonar.application.command.EsCommand;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.process.ProcessId;
-import org.sonar.process.command.JavaCommand;
-import org.sonar.process.jmvoptions.JvmOptions;
+import org.sonar.application.command.JavaCommand;
+import org.sonar.application.command.JvmOptions;
import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
import static org.assertj.core.api.Assertions.assertThat;
+++ /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.process.command;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import javax.annotation.Nullable;
-import org.sonar.process.ProcessId;
-import org.sonar.process.System2;
-
-import static java.util.Objects.requireNonNull;
-
-public abstract class AbstractCommand<T extends AbstractCommand> {
- // unique key among the group of commands to launch
- private final ProcessId id;
- // program arguments
- private final Map<String, String> arguments = new LinkedHashMap<>();
- private final Map<String, String> envVariables;
- private final Set<String> suppressedEnvVariables = new HashSet<>();
- private final File workDir;
-
- protected AbstractCommand(ProcessId id, File workDir, System2 system2) {
- this.id = requireNonNull(id, "ProcessId can't be null");
- this.workDir = requireNonNull(workDir, "workDir can't be null");
- this.envVariables = new HashMap<>(system2.getenv());
- }
-
- public ProcessId getProcessId() {
- return id;
- }
-
- public File getWorkDir() {
- return workDir;
- }
-
- @SuppressWarnings("unchecked")
- private T castThis() {
- return (T) this;
- }
-
- public Map<String, String> getArguments() {
- return arguments;
- }
-
- public T setArgument(String key, @Nullable String value) {
- if (value == null) {
- arguments.remove(key);
- } else {
- arguments.put(key, value);
- }
- return castThis();
- }
-
- public T setArguments(Properties args) {
- for (Map.Entry<Object, Object> entry : args.entrySet()) {
- setArgument(entry.getKey().toString(), entry.getValue() != null ? entry.getValue().toString() : null);
- }
- return castThis();
- }
-
- public Map<String, String> getEnvVariables() {
- return envVariables;
- }
-
- public Set<String> getSuppressedEnvVariables() {
- return suppressedEnvVariables;
- }
-
- public T suppressEnvVariable(String key) {
- requireNonNull(key, "key can't be null");
- suppressedEnvVariables.add(key);
- envVariables.remove(key);
- return castThis();
- }
-
- public T setEnvVariable(String key, String value) {
- envVariables.put(
- requireNonNull(key, "key can't be null"),
- requireNonNull(value, "value can't be null"));
- return castThis();
- }
-}
+++ /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.process.command;
-
-public interface CommandFactory {
-
- EsCommand createEsCommand();
-
- JavaCommand createWebCommand(boolean leader);
-
- JavaCommand createCeCommand();
-
-}
+++ /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.process.command;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Optional;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.System2;
-import org.sonar.process.es.EsFileSystem;
-import org.sonar.process.es.EsLogging;
-import org.sonar.process.es.EsSettings;
-import org.sonar.process.es.EsYmlSettings;
-import org.sonar.process.jmvoptions.CeJvmOptions;
-import org.sonar.process.jmvoptions.EsJvmOptions;
-import org.sonar.process.jmvoptions.JvmOptions;
-import org.sonar.process.jmvoptions.WebJvmOptions;
-
-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 {
- private static final String ENV_VAR_JAVA_TOOL_OPTIONS = "JAVA_TOOL_OPTIONS";
- /**
- * Properties about proxy that must be set as system properties
- */
- private static final String[] PROXY_PROPERTY_KEYS = new String[] {
- HTTP_PROXY_HOST,
- HTTP_PROXY_PORT,
- "http.nonProxyHosts",
- HTTPS_PROXY_HOST,
- HTTPS_PROXY_PORT,
- "http.auth.ntlm.domain",
- "socksProxyHost",
- "socksProxyPort"};
-
- private final Props props;
- private final File tempDir;
-
- public CommandFactoryImpl(Props props, File tempDir, System2 system2) {
- this.props = props;
- this.tempDir = tempDir;
- String javaToolOptions = system2.getenv(ENV_VAR_JAVA_TOOL_OPTIONS);
- if (javaToolOptions != null && !javaToolOptions.trim().isEmpty()) {
- LoggerFactory.getLogger(CommandFactoryImpl.class)
- .warn("JAVA_TOOL_OPTIONS is defined but will be ignored. " +
- "Use properties sonar.*.javaOpts and/or sonar.*.javaAdditionalOpts in sonar.properties to change SQ JVM processes options");
- }
- }
-
- @Override
- public EsCommand createEsCommand() {
- EsFileSystem esFileSystem = new EsFileSystem(props);
- if (!esFileSystem.getExecutable().exists()) {
- throw new IllegalStateException("Cannot find elasticsearch binary");
- }
- Map<String, String> settingsMap = new EsSettings(props, esFileSystem, System2.INSTANCE).build();
-
- return new EsCommand(ProcessId.ELASTICSEARCH, esFileSystem.getHomeDirectory())
- .setFileSystem(esFileSystem)
- .setLog4j2Properties(new EsLogging().createProperties(props, esFileSystem.getLogDirectory()))
- .setArguments(props.rawProperties())
- .setClusterName(settingsMap.get("cluster.name"))
- .setHost(settingsMap.get("network.host"))
- .setPort(Integer.valueOf(settingsMap.get("transport.tcp.port")))
- .addEsOption("-Epath.conf=" + esFileSystem.getConfDirectory().getAbsolutePath())
- .setEsJvmOptions(new EsJvmOptions()
- .addFromMandatoryProperty(props, ProcessProperties.SEARCH_JAVA_OPTS)
- .addFromMandatoryProperty(props, ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
- .setEsYmlSettings(new EsYmlSettings(settingsMap))
- .setEnvVariable("ES_JVM_OPTIONS", esFileSystem.getJvmOptions().getAbsolutePath())
- .setEnvVariable("JAVA_HOME", System.getProperties().getProperty("java.home"))
- .suppressEnvVariable(ENV_VAR_JAVA_TOOL_OPTIONS);
- }
-
- @Override
- public JavaCommand createWebCommand(boolean leader) {
- File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
-
- WebJvmOptions jvmOptions = new WebJvmOptions(tempDir)
- .addFromMandatoryProperty(props, ProcessProperties.WEB_JAVA_OPTS)
- .addFromMandatoryProperty(props, ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS);
- addProxyJvmOptions(jvmOptions);
-
- JavaCommand<WebJvmOptions> command = new JavaCommand<WebJvmOptions>(ProcessId.WEB_SERVER, homeDir)
- .setArguments(props.rawProperties())
- .setJvmOptions(jvmOptions)
- // required for logback tomcat valve
- .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS))
- .setArgument("sonar.cluster.web.startupLeader", Boolean.toString(leader))
- .setClassName("org.sonar.server.app.WebServer")
- .addClasspath("./lib/common/*")
- .addClasspath("./lib/server/*");
- String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
- if (driverPath != null) {
- command.addClasspath(driverPath);
- }
- command.suppressEnvVariable(ENV_VAR_JAVA_TOOL_OPTIONS);
- return command;
- }
-
- @Override
- public JavaCommand createCeCommand() {
- File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
-
- CeJvmOptions jvmOptions = new CeJvmOptions(tempDir)
- .addFromMandatoryProperty(props, ProcessProperties.CE_JAVA_OPTS)
- .addFromMandatoryProperty(props, ProcessProperties.CE_JAVA_ADDITIONAL_OPTS);
- addProxyJvmOptions(jvmOptions);
-
- JavaCommand<CeJvmOptions> command = new JavaCommand<CeJvmOptions>(ProcessId.COMPUTE_ENGINE, homeDir)
- .setArguments(props.rawProperties())
- .setJvmOptions(jvmOptions)
- .setClassName("org.sonar.ce.app.CeServer")
- .addClasspath("./lib/common/*")
- .addClasspath("./lib/server/*")
- .addClasspath("./lib/ce/*");
- String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
- if (driverPath != null) {
- command.addClasspath(driverPath);
- }
- command.suppressEnvVariable(ENV_VAR_JAVA_TOOL_OPTIONS);
- return command;
- }
-
- private <T extends JvmOptions> void addProxyJvmOptions(JvmOptions<T> jvmOptions) {
- for (String key : PROXY_PROPERTY_KEYS) {
- getPropsValue(key).ifPresent(val -> jvmOptions.add("-D" + key + "=" + val));
- }
-
- // defaults of HTTPS are the same than HTTP defaults
- setSystemPropertyToDefaultIfNotSet(jvmOptions, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
- setSystemPropertyToDefaultIfNotSet(jvmOptions, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
- }
-
- private void setSystemPropertyToDefaultIfNotSet(JvmOptions jvmOptions,
- String httpsProperty, String httpProperty) {
- Optional<String> httpValue = getPropsValue(httpProperty);
- Optional<String> httpsValue = getPropsValue(httpsProperty);
- if (!httpsValue.isPresent() && httpValue.isPresent()) {
- jvmOptions.add("-D" + httpsProperty + "=" + httpValue.get());
- }
- }
-
- private Optional<String> getPropsValue(String key) {
- return Optional.ofNullable(props.value(key));
- }
-}
+++ /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.process.command;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-import org.sonar.process.ProcessId;
-import org.sonar.process.System2;
-import org.sonar.process.es.EsFileSystem;
-import org.sonar.process.es.EsYmlSettings;
-import org.sonar.process.jmvoptions.EsJvmOptions;
-
-public class EsCommand extends AbstractCommand<EsCommand> {
- private EsFileSystem fileSystem;
- private String clusterName;
- private String host;
- private int port;
- private Properties log4j2Properties;
- private List<String> esOptions = new ArrayList<>();
- private EsJvmOptions esJvmOptions;
- private EsYmlSettings esYmlSettings;
-
- public EsCommand(ProcessId id, File workDir) {
- super(id, workDir, System2.INSTANCE);
- }
-
- public EsFileSystem getFileSystem() {
- return fileSystem;
- }
-
- public EsCommand setFileSystem(EsFileSystem fileSystem) {
- this.fileSystem = fileSystem;
- return this;
- }
-
- public String getClusterName() {
- return clusterName;
- }
-
- public EsCommand setClusterName(String clusterName) {
- this.clusterName = clusterName;
- return this;
- }
-
- public String getHost() {
- return host;
- }
-
- public EsCommand setHost(String host) {
- this.host = host;
- return this;
- }
-
- public int getPort() {
- return port;
- }
-
- public EsCommand setPort(int port) {
- this.port = port;
- return this;
- }
-
- public Properties getLog4j2Properties() {
- return log4j2Properties;
- }
-
- public EsCommand setLog4j2Properties(Properties log4j2Properties) {
- this.log4j2Properties = log4j2Properties;
- return this;
- }
-
- public List<String> getEsOptions() {
- return esOptions;
- }
-
- public EsCommand addEsOption(String s) {
- if (!s.isEmpty()) {
- esOptions.add(s);
- }
- return this;
- }
-
- public EsCommand setEsJvmOptions(EsJvmOptions esJvmOptions) {
- this.esJvmOptions = esJvmOptions;
- return this;
- }
-
- public EsJvmOptions getEsJvmOptions() {
- return esJvmOptions;
- }
-
- public EsCommand setEsYmlSettings(EsYmlSettings esYmlSettings) {
- this.esYmlSettings = esYmlSettings;
- return this;
- }
-
- public EsYmlSettings getEsYmlSettings() {
- return esYmlSettings;
- }
-}
+++ /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.process.command;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import org.sonar.process.ProcessId;
-import org.sonar.process.System2;
-import org.sonar.process.jmvoptions.JvmOptions;
-
-public class JavaCommand<T extends JvmOptions> extends AbstractCommand<JavaCommand<T>> {
- // entry point
- private String className;
- private JvmOptions<T> jvmOptions;
- // relative path to JAR files
- private final List<String> classpath = new ArrayList<>();
-
- public JavaCommand(ProcessId id, File workDir) {
- super(id, workDir, System2.INSTANCE);
- }
-
- public JvmOptions<T> getJvmOptions() {
- return jvmOptions;
- }
-
- public JavaCommand<T> setJvmOptions(JvmOptions<T> jvmOptions) {
- this.jvmOptions = jvmOptions;
-
- return this;
- }
-
- public String getClassName() {
- return className;
- }
-
- public JavaCommand<T> setClassName(String className) {
- this.className = className;
- return this;
- }
-
- public List<String> getClasspath() {
- return classpath;
- }
-
- public JavaCommand<T> addClasspath(String s) {
- classpath.add(s);
- return this;
- }
-
- @Override
- public String toString() {
- return "JavaCommand{" + "workDir=" + getWorkDir() +
- ", jvmOptions=" + jvmOptions +
- ", className='" + className + '\'' +
- ", classpath=" + classpath +
- ", arguments=" + getArguments() +
- ", envVariables=" + getEnvVariables() +
- ", suppressedEnvVariables=" + getSuppressedEnvVariables() +
- '}';
- }
-}
+++ /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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.process.command;
-
-import javax.annotation.ParametersAreNonnullByDefault;
+++ /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.process.es;
-
-import java.io.File;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-
-/**
- * Holds {@link File} to the various directories of ElasticSearch distribution embedded in SonarQube and provides
- * {@link File} objects to the various files of it SonarQube cares about.
- *
- * <p>
- * This class does not ensure files nor directories actually exist.
- * </p>
- */
-public class EsFileSystem {
- private final File homeDirectory;
- private final File dataDirectory;
- private final File confDirectory;
- private final File logDirectory;
-
- public EsFileSystem(Props props) {
- File sqHomeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
-
- this.homeDirectory = new File(sqHomeDir, "elasticsearch");
- this.dataDirectory = buildDataDir(props, sqHomeDir);
- this.confDirectory = buildConfDir(props);
- this.logDirectory = buildLogPath(props);
- }
-
- private static File buildDataDir(Props props, File sqHomeDir) {
- String dataPath = props.value(ProcessProperties.PATH_DATA);
- if (StringUtils.isNotEmpty(dataPath)) {
- return new File(dataPath, "es");
- }
- return new File(sqHomeDir, "data/es");
- }
-
- private static File buildLogPath(Props props) {
- return props.nonNullValueAsFile(ProcessProperties.PATH_LOGS);
- }
-
- private static File buildConfDir(Props props) {
- File tempPath = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP);
- return new File(new File(tempPath, "conf"), "es");
- }
-
- public File getHomeDirectory() {
- return homeDirectory;
- }
-
- public File getDataDirectory() {
- return dataDirectory;
- }
-
- public File getConfDirectory() {
- return confDirectory;
- }
-
- public File getLogDirectory() {
- return logDirectory;
- }
-
- public File getExecutable() {
- return new File(homeDirectory, "bin/" + getExecutableName());
- }
-
- private static String getExecutableName() {
- if (System.getProperty("os.name").startsWith("Windows")) {
- return "elasticsearch.bat";
- }
- return "elasticsearch";
- }
-
- public File getLog4j2Properties() {
- return new File(confDirectory, "log4j2.properties");
- }
-
- public File getElasticsearchYml() {
- return new File(confDirectory, "elasticsearch.yml");
- }
-
- public File getJvmOptions() {
- return new File(confDirectory, "jvm.options");
- }
-}
+++ /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.process.es;
-
-import ch.qos.logback.classic.Level;
-import java.io.File;
-import java.util.Properties;
-import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
-import org.sonar.process.logging.Log4JPropertiesBuilder;
-import org.sonar.process.logging.LogLevelConfig;
-import org.sonar.process.logging.RootLoggerConfig;
-
-import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
-
-public class EsLogging {
-
- public Properties createProperties(Props props, File logDir) {
- Log4JPropertiesBuilder log4JPropertiesBuilder = new Log4JPropertiesBuilder(props);
- RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build();
- String logPattern = log4JPropertiesBuilder.buildLogPattern(config);
-
- log4JPropertiesBuilder.internalLogLevel(Level.ERROR);
- log4JPropertiesBuilder.configureGlobalFileLog(config, logDir, logPattern);
- log4JPropertiesBuilder.apply(
- LogLevelConfig.newBuilder(log4JPropertiesBuilder.getRootLoggerName())
- .rootLevelFor(ProcessId.ELASTICSEARCH)
- .build());
-
- return log4JPropertiesBuilder.get();
- }
-
-}
+++ /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.process.es;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.System2;
-
-import static java.lang.String.valueOf;
-import static org.sonar.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
-
-public class EsSettings {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(EsSettings.class);
- private static final String STANDALONE_NODE_NAME = "sonarqube";
-
- private final Props props;
- private final EsFileSystem fileSystem;
-
- private final boolean clusterEnabled;
- private final String clusterName;
- private final String nodeName;
-
- public EsSettings(Props props, EsFileSystem fileSystem, System2 system2) {
- this.props = props;
- this.fileSystem = fileSystem;
-
- this.clusterName = props.nonNullValue(CLUSTER_NAME);
- this.clusterEnabled = props.valueAsBoolean(CLUSTER_ENABLED);
- if (this.clusterEnabled) {
- this.nodeName = props.value(CLUSTER_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString());
- } else {
- this.nodeName = STANDALONE_NODE_NAME;
- }
- String esJvmOptions = system2.getenv("ES_JVM_OPTIONS");
- if (esJvmOptions != null && !esJvmOptions.trim().isEmpty()) {
- LOGGER.warn("ES_JVM_OPTIONS is defined but will be ignored. " +
- "Use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch");
- }
- }
-
- public Map<String, String> build() {
- Map<String, String> builder = new HashMap<>();
- configureFileSystem(builder);
- configureNetwork(builder);
- configureCluster(builder);
- configureAction(builder);
- return builder;
- }
-
- private void configureFileSystem(Map<String, String> builder) {
- builder.put("path.data", fileSystem.getDataDirectory().getAbsolutePath());
- builder.put("path.conf", fileSystem.getConfDirectory().getAbsolutePath());
- builder.put("path.logs", fileSystem.getLogDirectory().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);
- }
- }
-
- 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(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 static void configureAction(Map<String, String> builder) {
- builder.put("action.auto_create_index", String.valueOf(false));
- }
-}
+++ /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.process.es;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.Map;
-import org.yaml.snakeyaml.DumperOptions;
-import org.yaml.snakeyaml.Yaml;
-
-import static org.yaml.snakeyaml.DumperOptions.FlowStyle.BLOCK;
-
-public class EsYmlSettings {
- private static final String ELASTICSEARCH_YML_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
- "\n" +
- "# DO NOT EDIT THIS FILE\n" +
- "\n";
-
- private final Map<String, String> elasticsearchSettings;
-
- public EsYmlSettings(Map<String, String> elasticsearchSettings) {
- this.elasticsearchSettings = elasticsearchSettings;
- }
-
- public void writeToYmlSettingsFile(File file) {
- DumperOptions dumperOptions = new DumperOptions();
- dumperOptions.setPrettyFlow(true);
- dumperOptions.setDefaultFlowStyle(BLOCK);
- Yaml yaml = new Yaml(dumperOptions);
- String output = ELASTICSEARCH_YML_OPTIONS_HEADER + yaml.dump(elasticsearchSettings);
- try {
- Files.write(file.toPath(), output.getBytes(Charset.forName("UTF-8")));
- } catch (IOException e) {
- throw new IllegalStateException("Cannot write Elasticsearch yml settings file", e);
- }
- }
-}
+++ /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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.process.es;
-
-import javax.annotation.ParametersAreNonnullByDefault;
+++ /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.process.jmvoptions;
-
-import java.io.File;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class CeJvmOptions extends JvmOptions<CeJvmOptions> {
- public CeJvmOptions(File tmpDir) {
- super(mandatoryOptions(tmpDir));
- }
-
- private static Map<String, String> mandatoryOptions(File tmpDir) {
- Map<String, String> res = new LinkedHashMap<>(3);
- res.put("-Djava.awt.headless=", "true");
- res.put("-Dfile.encoding=", "UTF-8");
- res.put("-Djava.io.tmpdir=", tmpDir.getAbsolutePath());
- return res;
- }
-}
+++ /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.process.jmvoptions;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public class EsJvmOptions extends JvmOptions<EsJvmOptions> {
- private static final String ELASTICSEARCH_JVM_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
- "# Please use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
- "\n" +
- "# DO NOT EDIT THIS FILE\n" +
- "\n";
-
- public EsJvmOptions() {
- super(mandatoryOptions());
- }
-
- private static Map<String, String> mandatoryOptions() {
- Map<String, String> res = new LinkedHashMap<>(16);
- res.put("-XX:+UseConcMarkSweepGC", "");
- res.put("-XX:CMSInitiatingOccupancyFraction=", "75");
- res.put("-XX:+UseCMSInitiatingOccupancyOnly", "");
- res.put("-XX:+AlwaysPreTouch", "");
- res.put("-server", "");
- res.put("-Xss", "1m");
- res.put("-Djava.awt.headless=", "true");
- res.put("-Dfile.encoding=", "UTF-8");
- res.put("-Djna.nosys=", "true");
- res.put("-Djdk.io.permissionsUseCanonicalPath=", "true");
- res.put("-Dio.netty.noUnsafe=", "true");
- res.put("-Dio.netty.noKeySetOptimization=", "true");
- res.put("-Dio.netty.recycler.maxCapacityPerThread=", "0");
- res.put("-Dlog4j.shutdownHookEnabled=", "false");
- res.put("-Dlog4j2.disable.jmx=", "true");
- res.put("-Dlog4j.skipJansi=", "true");
- return res;
- }
-
- public void writeToJvmOptionFile(File file) {
- String jvmOptions = getAll().stream().collect(Collectors.joining("\n"));
- String jvmOptionsContent = ELASTICSEARCH_JVM_OPTIONS_HEADER + jvmOptions;
- try {
- Files.write(file.toPath(), jvmOptionsContent.getBytes(Charset.forName("UTF-8")));
- } catch (IOException e) {
- throw new IllegalStateException("Cannot write Elasticsearch jvm options file", e);
- }
- }
-}
+++ /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.process.jmvoptions;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.process.MessageException;
-import org.sonar.process.Props;
-
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static java.util.stream.Collectors.joining;
-
-public class JvmOptions<T extends JvmOptions> {
- private static final String JVM_OPTION_NOT_NULL_ERROR_MESSAGE = "a JVM option can't be null";
-
- private final HashMap<String, String> mandatoryOptions = new HashMap<>();
- private final LinkedHashSet<String> options = new LinkedHashSet<>();
-
- public JvmOptions() {
- this(Collections.emptyMap());
- }
-
- public JvmOptions(Map<String, String> mandatoryJvmOptions) {
- requireNonNull(mandatoryJvmOptions, JVM_OPTION_NOT_NULL_ERROR_MESSAGE)
- .entrySet()
- .stream()
- .filter(e -> {
- requireNonNull(e.getKey(), "JVM option prefix can't be null");
- if (e.getKey().trim().isEmpty()) {
- throw new IllegalArgumentException("JVM option prefix can't be empty");
- }
- requireNonNull(e.getValue(), "JVM option value can't be null");
- return true;
- }).forEach(e -> {
- String key = e.getKey().trim();
- String value = e.getValue().trim();
- mandatoryOptions.put(key, value);
- add(key + value);
- });
- }
-
- public T addFromMandatoryProperty(Props props, String propertyName) {
- String value = props.nonNullValue(propertyName);
- if (!value.isEmpty()) {
- List<String> jvmOptions = Arrays.stream(value.split(" (?=-)")).map(String::trim).collect(Collectors.toList());
- checkOptionFormat(propertyName, jvmOptions);
- checkMandatoryOptionOverwrite(propertyName, jvmOptions);
- options.addAll(jvmOptions);
- }
-
- return castThis();
- }
-
- private static void checkOptionFormat(String propertyName, List<String> jvmOptionsFromProperty) {
- List<String> invalidOptions = jvmOptionsFromProperty.stream()
- .filter(JvmOptions::isInvalidOption)
- .collect(Collectors.toList());
- if (!invalidOptions.isEmpty()) {
- throw new MessageException(format(
- "a JVM option can't be empty and must start with '-'. The following JVM options defined by property '%s' are invalid: %s",
- propertyName,
- invalidOptions.stream()
- .collect(joining(", "))));
- }
- }
-
- private void checkMandatoryOptionOverwrite(String propertyName, List<String> jvmOptionsFromProperty) {
- List<Match> matches = jvmOptionsFromProperty.stream()
- .map(jvmOption -> new Match(jvmOption, mandatoryOptionFor(jvmOption)))
- .filter(match -> match.getMandatoryOption() != null)
- .collect(Collectors.toList());
- if (!matches.isEmpty()) {
- throw new MessageException(format(
- "a JVM option can't overwrite mandatory JVM options. The following JVM options defined by property '%s' are invalid: %s",
- propertyName,
- matches.stream()
- .map(m -> m.getOption() + " overwrites " + m.mandatoryOption.getKey() + m.mandatoryOption.getValue())
- .collect(joining(", "))));
- }
- }
-
- /**
- * Add an option.
- * Argument is trimmed before being added.
- *
- * @throws IllegalArgumentException if argument is empty or does not start with {@code -}.
- */
- public T add(String str) {
- requireNonNull(str, JVM_OPTION_NOT_NULL_ERROR_MESSAGE);
- String value = str.trim();
- if (isInvalidOption(value)) {
- throw new IllegalArgumentException("a JVM option can't be empty and must start with '-'");
- }
- checkMandatoryOptionOverwrite(value);
- options.add(value);
-
- return castThis();
- }
-
- private void checkMandatoryOptionOverwrite(String value) {
- Map.Entry<String, String> overriddenMandatoryOption = mandatoryOptionFor(value);
- if (overriddenMandatoryOption != null) {
- throw new MessageException(String.format(
- "a JVM option can't overwrite mandatory JVM options. %s overwrites %s",
- value,
- overriddenMandatoryOption.getKey() + overriddenMandatoryOption.getValue()));
- }
- }
-
- @CheckForNull
- private Map.Entry<String, String> mandatoryOptionFor(String jvmOption) {
- return mandatoryOptions.entrySet().stream()
- .filter(s -> jvmOption.startsWith(s.getKey()) && !jvmOption.equals(s.getKey() + s.getValue()))
- .findFirst()
- .orElse(null);
- }
-
- private static boolean isInvalidOption(String value) {
- return value.isEmpty() || !value.startsWith("-");
- }
-
- @SuppressWarnings("unchecked")
- private T castThis() {
- return (T) this;
- }
-
- public List<String> getAll() {
- return new ArrayList<>(options);
- }
-
- @Override
- public String toString() {
- return options.toString();
- }
-
- private static final class Match {
- private final String option;
-
- private final Map.Entry<String, String> mandatoryOption;
-
- private Match(String option, @Nullable Map.Entry<String, String> mandatoryOption) {
- this.option = option;
- this.mandatoryOption = mandatoryOption;
- }
-
- String getOption() {
- return option;
- }
-
- @CheckForNull
- Map.Entry<String, String> getMandatoryOption() {
- return mandatoryOption;
- }
-
- }
-}
+++ /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.process.jmvoptions;
-
-import java.io.File;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class WebJvmOptions extends JvmOptions<WebJvmOptions> {
- public WebJvmOptions(File tmpDir) {
- super(mandatoryOptions(tmpDir));
- }
-
- private static Map<String, String> mandatoryOptions(File tmpDir) {
- Map<String, String> res = new LinkedHashMap<>(3);
- res.put("-Djava.awt.headless=", "true");
- res.put("-Dfile.encoding=", "UTF-8");
- res.put("-Djava.io.tmpdir=", tmpDir.getAbsolutePath());
- return res;
- }
-}
+++ /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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.process.jmvoptions;
-
-import javax.annotation.ParametersAreNonnullByDefault;
+++ /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.process.command;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Random;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
-import org.sonar.process.ProcessId;
-import org.sonar.process.System2;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.when;
-
-public class AbstractCommandTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void constructor_throws_NPE_of_ProcessId_is_null() throws IOException {
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("ProcessId can't be null");
-
- new AbstractCommand<AbstractCommand>(null, temp.newFolder(), System2.INSTANCE) {
-
- };
- }
-
- @Test
- public void constructor_throws_NPE_of_workDir_is_null() throws IOException {
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("workDir can't be null");
-
- new AbstractCommand<AbstractCommand>(ProcessId.WEB_SERVER, null, System2.INSTANCE) {
-
- };
- }
-
- @Test
- public void test_command_with_complete_information() throws Exception {
- File workDir = temp.newFolder();
- AbstractCommand command = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, System2.INSTANCE) {
-
- };
-
- command.setArgument("first_arg", "val1");
- Properties args = new Properties();
- args.setProperty("second_arg", "val2");
- command.setArguments(args);
-
- command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
-
- assertThat(command.toString()).isNotNull();
- assertThat(command.getWorkDir()).isSameAs(workDir);
-
- // copy current env variables
- assertThat(command.getEnvVariables().get("JAVA_COMMAND_TEST")).isEqualTo("1000");
- assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1);
- }
-
- @Test
- public void setEnvVariable_fails_with_NPE_if_key_is_null() throws IOException {
- File workDir = temp.newFolder();
- AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, System2.INSTANCE) {
-
- };
-
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("key can't be null");
-
- underTest.setEnvVariable(null, randomAlphanumeric(30));
- }
-
- @Test
- public void setEnvVariable_fails_with_NPE_if_value_is_null() throws IOException {
- File workDir = temp.newFolder();
- AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, System2.INSTANCE) {
-
- };
-
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("value can't be null");
-
- underTest.setEnvVariable(randomAlphanumeric(30), null);
- }
-
- @Test
- public void constructor_puts_System_getEnv_into_map_of_env_variables() throws IOException {
- File workDir = temp.newFolder();
- System2 system2 = Mockito.mock(System2.class);
- Map<String, String> env = IntStream.range(0, 1 + new Random().nextInt(99)).mapToObj(String::valueOf).collect(Collectors.toMap(i -> "key" + i, j -> "value" + j));
- when(system2.getenv()).thenReturn(env);
- AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, system2) {
-
- };
-
- assertThat(underTest.getEnvVariables()).isEqualTo(env);
- }
-
- @Test
- public void suppressEnvVariable_remove_existing_env_variable_and_add_variable_to_set_of_suppressed_variables() throws IOException {
- File workDir = temp.newFolder();
- System2 system2 = Mockito.mock(System2.class);
- Map<String, String> env = new HashMap<>();
- String key1 = randomAlphanumeric(3);
- env.put(key1, randomAlphanumeric(9));
- when(system2.getenv()).thenReturn(env);
- AbstractCommand underTest = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir, system2) {
-
- };
-
- underTest.suppressEnvVariable(key1);
-
- assertThat(underTest.getEnvVariables()).doesNotContainKey(key1);
- assertThat(underTest.getSuppressedEnvVariables()).containsOnly(key1);
- }
-
-}
+++ /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.process.command;
-
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-import org.apache.commons.io.FileUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.System2;
-import org.sonar.process.logging.ListAppender;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.when;
-
-public class CommandFactoryImplTest {
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- private File homeDir;
- private File tempDir;
- private File logsDir;
- private ListAppender listAppender;
-
- @Before
- public void setUp() throws Exception {
- homeDir = temp.newFolder();
- tempDir = temp.newFolder();
- logsDir = temp.newFolder();
- }
-
- @After
- public void tearDown() throws Exception {
- if (listAppender != null) {
- ListAppender.detachMemoryAppenderToLoggerOf(CommandFactoryImpl.class, listAppender);
- }
- }
-
- @Test
- public void constructor_logs_no_warning_if_env_variable_JAVA_TOOL_OPTIONS_is_not_set() {
- System2 system2 = Mockito.mock(System2.class);
- when(system2.getenv(anyString())).thenReturn(null);
- attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
-
- new CommandFactoryImpl(new Props(new Properties()), tempDir, system2);
-
- assertThat(listAppender.getLogs()).isEmpty();
- }
-
- @Test
- public void constructor_logs_warning_if_env_variable_JAVA_TOOL_OPTIONS_is_set() {
- System2 system2 = Mockito.mock(System2.class);
- when(system2.getenv("JAVA_TOOL_OPTIONS")).thenReturn("sds");
- attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
-
- new CommandFactoryImpl(new Props(new Properties()), tempDir, system2);
-
- assertThat(listAppender.getLogs())
- .extracting(ILoggingEvent::getMessage)
- .containsOnly(
- "JAVA_TOOL_OPTIONS is defined but will be ignored. " +
- "Use properties sonar.*.javaOpts and/or sonar.*.javaAdditionalOpts in sonar.properties to change SQ JVM processes options");
- }
-
- @Test
- public void createEsCommand_throws_ISE_if_es_binary_is_not_found() throws Exception {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Cannot find elasticsearch binary");
-
- newFactory(new Properties()).createEsCommand();
- }
-
- @Test
- public void createEsCommand_returns_command_for_default_settings() throws Exception {
- prepareEsFileSystem();
-
- Properties props = new Properties();
- props.setProperty("sonar.search.host", "localhost");
-
- EsCommand esCommand = newFactory(props).createEsCommand();
-
- assertThat(esCommand.getClusterName()).isEqualTo("sonarqube");
- assertThat(esCommand.getHost()).isNotEmpty();
- assertThat(esCommand.getPort()).isEqualTo(9001);
- assertThat(esCommand.getEsJvmOptions().getAll())
- // enforced values
- .contains("-XX:+UseConcMarkSweepGC", "-server", "-Dfile.encoding=UTF-8")
- // default settings
- .contains("-Xms512m", "-Xmx512m", "-XX:+HeapDumpOnOutOfMemoryError");
- File esConfDir = new File(tempDir, "conf/es");
- assertThat(esCommand.getEsOptions()).containsOnly("-Epath.conf=" + esConfDir.getAbsolutePath());
- assertThat(esCommand.getEnvVariables())
- .contains(entry("ES_JVM_OPTIONS", new File(esConfDir, "jvm.options").getAbsolutePath()))
- .containsKey("JAVA_HOME");
- assertThat(esCommand.getEsYmlSettings()).isNotNull();
-
- assertThat(esCommand.getLog4j2Properties())
- .contains(entry("appender.file_es.fileName", new File(logsDir, "es.log").getAbsolutePath()));
-
- assertThat(esCommand.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
- }
-
- @Test
- public void createEsCommand_returns_command_for_overridden_settings() throws Exception {
- prepareEsFileSystem();
-
- Properties props = new Properties();
- props.setProperty("sonar.search.host", "localhost");
- props.setProperty("sonar.cluster.name", "foo");
- props.setProperty("sonar.search.port", "1234");
- props.setProperty("sonar.search.javaOpts", "-Xms10G -Xmx10G");
-
- EsCommand command = newFactory(props).createEsCommand();
-
- assertThat(command.getClusterName()).isEqualTo("foo");
- assertThat(command.getPort()).isEqualTo(1234);
- assertThat(command.getEsJvmOptions().getAll())
- // enforced values
- .contains("-XX:+UseConcMarkSweepGC", "-server", "-Dfile.encoding=UTF-8")
- // user settings
- .contains("-Xms10G", "-Xmx10G")
- // default values disabled
- .doesNotContain("-XX:+HeapDumpOnOutOfMemoryError");
- }
-
- @Test
- public void createWebCommand_returns_command_for_default_settings() throws Exception {
- JavaCommand command = newFactory(new Properties()).createWebCommand(true);
-
- assertThat(command.getClassName()).isEqualTo("org.sonar.server.app.WebServer");
- assertThat(command.getWorkDir().getAbsolutePath()).isEqualTo(homeDir.getAbsolutePath());
- assertThat(command.getClasspath())
- .containsExactlyInAnyOrder("./lib/common/*", "./lib/server/*");
- assertThat(command.getJvmOptions().getAll())
- // enforced values
- .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
- // default settings
- .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
- .contains("-Xmx512m", "-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
- assertThat(command.getProcessId()).isEqualTo(ProcessId.WEB_SERVER);
- assertThat(command.getEnvVariables())
- .containsKey("JAVA_HOME");
- assertThat(command.getArguments())
- // default settings
- .contains(entry("sonar.web.javaOpts", "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError"))
- .contains(entry("sonar.cluster.enabled", "false"));
-
- assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
- }
-
- @Test
- public void createWebCommand_configures_command_with_overridden_settings() throws Exception {
- Properties props = new Properties();
- props.setProperty("sonar.web.port", "1234");
- props.setProperty("sonar.web.javaOpts", "-Xmx10G");
- JavaCommand command = newFactory(props).createWebCommand(true);
-
- assertThat(command.getJvmOptions().getAll())
- // enforced values
- .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
- // default settings
- .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
- // overridden values
- .contains("-Xmx10G")
- .doesNotContain("-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
- assertThat(command.getArguments())
- // default settings
- .contains(entry("sonar.web.javaOpts", "-Xmx10G"))
- .contains(entry("sonar.cluster.enabled", "false"));
-
- assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
- }
-
- @Test
- public void createWebCommand_adds_configured_jdbc_driver_to_classpath() throws Exception {
- Properties props = new Properties();
- File driverFile = temp.newFile();
- props.setProperty("sonar.jdbc.driverPath", driverFile.getAbsolutePath());
-
- JavaCommand command = newFactory(props).createWebCommand(true);
-
- assertThat(command.getClasspath())
- .containsExactlyInAnyOrder("./lib/common/*", "./lib/server/*", driverFile.getAbsolutePath());
- }
-
- private void prepareEsFileSystem() throws IOException {
- FileUtils.touch(new File(homeDir, "elasticsearch/bin/elasticsearch"));
- FileUtils.touch(new File(homeDir, "elasticsearch/bin/elasticsearch.bat"));
- }
-
- private CommandFactory newFactory(Properties userProps) throws IOException {
- Properties p = new Properties();
- p.setProperty("sonar.path.home", homeDir.getAbsolutePath());
- p.setProperty("sonar.path.temp", tempDir.getAbsolutePath());
- p.setProperty("sonar.path.logs", logsDir.getAbsolutePath());
- p.putAll(userProps);
-
- Props props = new Props(p);
- ProcessProperties.completeDefaults(props);
- return new CommandFactoryImpl(props, tempDir, System2.INSTANCE);
- }
-
- private <T> void attachMemoryAppenderToLoggerOf(Class<T> loggerClass) {
- this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(loggerClass);
- }
-
-}
+++ /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.process.command;
-
-import java.io.File;
-import java.util.Properties;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.process.ProcessId;
-import org.sonar.process.jmvoptions.JvmOptions;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class JavaCommandTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- @Test
- public void test_command_with_complete_information() throws Exception {
- File workDir = temp.newFolder();
- JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, workDir);
-
- command.setArgument("first_arg", "val1");
- Properties args = new Properties();
- args.setProperty("second_arg", "val2");
- command.setArguments(args);
-
- command.setClassName("org.sonar.ElasticSearch");
- command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
- command.addClasspath("lib/*.jar");
- command.addClasspath("conf/*.xml");
- JvmOptions<JvmOptions> jvmOptions = new JvmOptions<JvmOptions>() {};
- command.setJvmOptions(jvmOptions);
-
- assertThat(command.toString()).isNotNull();
- assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml");
- assertThat(command.getJvmOptions()).isSameAs(jvmOptions);
- assertThat(command.getWorkDir()).isSameAs(workDir);
- assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch");
-
- // copy current env variables
- assertThat(command.getEnvVariables().get("JAVA_COMMAND_TEST")).isEqualTo("1000");
- assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1);
- }
-
-}
+++ /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.process.es;
-
-import java.io.File;
-import java.io.IOException;
-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 EsFileSystemTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void constructor_fails_with_IAE_if_sq_home_property_is_not_defined() {
- Props props = new Props(new Properties());
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Property sonar.path.home is not set");
-
- new EsFileSystem(props);
- }
-
- @Test
- public void constructor_fails_with_IAE_if_temp_dir_property_is_not_defined() throws IOException {
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Property sonar.path.temp is not set");
-
- new EsFileSystem(props);
- }
-
- @Test
- public void getHomeDirectory_is_elasticsearch_subdirectory_of_sq_home_directory() throws IOException {
- File sqHomeDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getHomeDirectory()).isEqualTo(new File(sqHomeDir, "elasticsearch"));
- }
-
- @Test
- public void getDataDirectory_is_data_es_subdirectory_of_sq_home_directory_by_default() throws IOException {
- File sqHomeDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getDataDirectory()).isEqualTo(new File(sqHomeDir, "data/es"));
- }
-
- @Test
- public void override_data_dir() throws Exception {
- File sqHomeDir = temp.newFolder();
- File tempDir = temp.newFolder();
- File dataDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- props.set(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getDataDirectory()).isEqualTo(new File(dataDir, "es"));
- }
-
- @Test
- public void getLogDirectory_is_configured_with_non_nullable_PATH_LOG_variable() throws IOException {
- File sqHomeDir = temp.newFolder();
- File logDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getLogDirectory()).isEqualTo(logDir);
- }
-
- @Test
- public void conf_directory_is_conf_es_subdirectory_of_sq_temp_directory() throws IOException {
- File tempDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getConfDirectory()).isEqualTo(new File(tempDir, "conf/es"));
- }
-
- @Test
- public void getExecutable_resolve_executable_for_platform() throws IOException {
- File sqHomeDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, sqHomeDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- if (System.getProperty("os.name").startsWith("Windows")) {
- assertThat(underTest.getExecutable()).isEqualTo(new File(sqHomeDir, "elasticsearch/bin/elasticsearch.bat"));
- } else {
- assertThat(underTest.getExecutable()).isEqualTo(new File(sqHomeDir, "elasticsearch/bin/elasticsearch"));
- }
- }
-
- @Test
- public void getLog4j2Properties_is_in_es_conf_directory() throws IOException {
- File tempDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getLog4j2Properties()).isEqualTo(new File(tempDir, "conf/es/log4j2.properties"));
- }
-
- @Test
- public void getElasticsearchYml_is_in_es_conf_directory() throws IOException {
- File tempDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getElasticsearchYml()).isEqualTo(new File(tempDir, "conf/es/elasticsearch.yml"));
- }
-
- @Test
- public void getJvmOptions_is_in_es_conf_directory() throws IOException {
- File tempDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
-
- EsFileSystem underTest = new EsFileSystem(props);
-
- assertThat(underTest.getJvmOptions()).isEqualTo(new File(tempDir, "conf/es/jvm.options"));
- }
-}
+++ /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.process.es;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.process.Props;
-import org.sonar.process.es.EsLogging;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class EsLoggingTest {
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- private EsLogging underTest = new EsLogging();
-
- @Test
- public void createProperties_with_empty_props() throws IOException {
- File logDir = temporaryFolder.newFolder();
- Properties properties = underTest.createProperties(newProps(), logDir);
-
- verifyProperties(properties,
- "status", "ERROR",
- "appender.file_es.type", "RollingFile",
- "appender.file_es.name", "file_es",
- "appender.file_es.filePattern", new File(logDir, "es.%d{yyyy-MM-dd}.log").getAbsolutePath(),
- "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(),
- "appender.file_es.layout.type", "PatternLayout",
- "appender.file_es.layout.pattern", "%d{yyyy.MM.dd HH:mm:ss} %-5level es[][%logger{1.}] %msg%n",
- "appender.file_es.policies.type", "Policies",
- "appender.file_es.policies.time.type", "TimeBasedTriggeringPolicy",
- "appender.file_es.policies.time.interval", "1",
- "appender.file_es.policies.time.modulate", "true",
- "appender.file_es.strategy.type", "DefaultRolloverStrategy",
- "appender.file_es.strategy.fileIndex", "nomax",
- "appender.file_es.strategy.action.type", "Delete",
- "appender.file_es.strategy.action.basepath", logDir.getAbsolutePath(),
- "appender.file_es.strategy.action.maxDepth", "1",
- "appender.file_es.strategy.action.condition.type", "IfFileName",
- "appender.file_es.strategy.action.condition.glob", "es*",
- "appender.file_es.strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount",
- "appender.file_es.strategy.action.condition.nested_condition.exceeds", "7",
- "rootLogger.level", "INFO",
- "rootLogger.appenderRef.file_es.ref", "file_es");
- }
-
- @Test
- public void createProperties_sets_root_logger_to_INFO_if_no_property_is_set() throws IOException {
- File logDir = temporaryFolder.newFolder();
- Properties properties = underTest.createProperties(newProps(), logDir);
-
- assertThat(properties.getProperty("rootLogger.level")).isEqualTo("INFO");
- }
-
- @Test
- public void createProperties_sets_root_logger_to_global_property_if_set() throws IOException {
- File logDir = temporaryFolder.newFolder();
- Properties properties = underTest.createProperties(newProps("sonar.log.level", "TRACE"), logDir);
-
- assertThat(properties.getProperty("rootLogger.level")).isEqualTo("TRACE");
- }
-
- @Test
- public void createProperties_sets_root_logger_to_process_property_if_set() throws IOException {
- File logDir = temporaryFolder.newFolder();
- Properties properties = underTest.createProperties(newProps("sonar.log.level.es", "DEBUG"), logDir);
-
- assertThat(properties.getProperty("rootLogger.level")).isEqualTo("DEBUG");
- }
-
- @Test
- public void createProperties_sets_root_logger_to_process_property_over_global_property_if_both_set() throws IOException {
- File logDir = temporaryFolder.newFolder();
- Properties properties = underTest.createProperties(
- newProps(
- "sonar.log.level", "DEBUG",
- "sonar.log.level.es", "TRACE"),
- logDir);
-
- assertThat(properties.getProperty("rootLogger.level")).isEqualTo("TRACE");
- }
-
- private static Props newProps(String... propertyKeysAndValues) {
- assertThat(propertyKeysAndValues.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
- Properties properties = new Properties();
- for (int i = 0; i < propertyKeysAndValues.length; i++) {
- properties.put(propertyKeysAndValues[i++], propertyKeysAndValues[i]);
- }
- return new Props(properties);
- }
-
- private void verifyProperties(Properties properties, String... expectedPropertyKeysAndValuesOrdered) {
- if (expectedPropertyKeysAndValuesOrdered.length == 0) {
- assertThat(properties.size()).isEqualTo(0);
- } else {
- assertThat(expectedPropertyKeysAndValuesOrdered.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
- Set<String> keys = new HashSet<>(expectedPropertyKeysAndValuesOrdered.length / 2 + 1);
- keys.add("status");
- for (int i = 0; i < expectedPropertyKeysAndValuesOrdered.length; i++) {
- String key = expectedPropertyKeysAndValuesOrdered[i++];
- String value = expectedPropertyKeysAndValuesOrdered[i];
- assertThat(properties.get(key)).describedAs("Unexpected value for property " + key).isEqualTo(value);
- keys.add(key);
- }
- assertThat(properties.keySet()).containsOnly(keys.toArray());
- }
- }
-}
+++ /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.process.es;
-
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Random;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.cluster.ClusterProperties;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.System2;
-import org.sonar.process.logging.ListAppender;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
-
-public class EsSettingsTest {
-
- private static final boolean CLUSTER_ENABLED = true;
- private static final boolean CLUSTER_DISABLED = false;
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
- private ListAppender listAppender;
-
- @After
- public void tearDown() throws Exception {
- if (listAppender != null) {
- ListAppender.detachMemoryAppenderToLoggerOf(EsSettings.class, listAppender);
- }
- }
-
- @Test
- public void constructor_does_not_logs_warning_if_env_variable_ES_JVM_OPTIONS_is_not_set() {
- this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(EsSettings.class);
- Props props = minimalProps();
- System2 system2 = mock(System2.class);
- new EsSettings(props, new EsFileSystem(props), system2);
-
- assertThat(listAppender.getLogs()).isEmpty();
- }
-
- @Test
- public void constructor_does_not_logs_warning_if_env_variable_ES_JVM_OPTIONS_is_set_and_empty() {
- this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(EsSettings.class);
- Props props = minimalProps();
- System2 system2 = mock(System2.class);
- when(system2.getenv("ES_JVM_OPTIONS")).thenReturn(" ");
- new EsSettings(props, new EsFileSystem(props), system2);
-
- assertThat(listAppender.getLogs()).isEmpty();
- }
-
- @Test
- public void constructor_logs_warning_if_env_variable_ES_JVM_OPTIONS_is_set_and_non_empty() throws IOException {
- this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(EsSettings.class);
- Props props = minimalProps();
- System2 system2 = mock(System2.class);
- when(system2.getenv("ES_JVM_OPTIONS")).thenReturn(randomAlphanumeric(2));
- new EsSettings(props, new EsFileSystem(props), system2);
-
- assertThat(listAppender.getLogs())
- .extracting(ILoggingEvent::getMessage)
- .containsOnly("ES_JVM_OPTIONS is defined but will be ignored. " +
- "Use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch");
- }
-
- private Props minimalProps() {
- Props props = new Props(new Properties());
- props.set(ProcessProperties.PATH_HOME, randomAlphanumeric(12));
- props.set(ProcessProperties.PATH_TEMP, randomAlphanumeric(12));
- props.set(ProcessProperties.PATH_LOGS, randomAlphanumeric(12));
- props.set(CLUSTER_NAME, randomAlphanumeric(12));
- return props;
- }
-
- @Test
- public void test_default_settings_for_standalone_mode() 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
- props.set(CLUSTER_NAME, "sonarqube");
-
- EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
-
- 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")).isNull();
- assertThat(generated.get("path.conf")).isNotNull();
-
- // http is disabled for security reasons
- assertThat(generated.get("http.enabled")).isEqualTo("false");
-
- 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");
-
- assertThat(generated.get("action.auto_create_index")).isEqualTo("false");
- }
-
- @Test
- public void test_default_settings_for_cluster_mode() 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
- props.set(ClusterProperties.CLUSTER_NAME, "sonarqube-1");
- props.set(ClusterProperties.CLUSTER_ENABLED, "true");
- props.set(ClusterProperties.CLUSTER_NODE_NAME, "node-1");
-
- EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
-
- Map<String, String> generated = esSettings.build();
- assertThat(generated.get("cluster.name")).isEqualTo("sonarqube-1");
- assertThat(generated.get("node.name")).isEqualTo("node-1");
- }
-
- @Test
- public void test_node_name_default_for_cluster_mode() throws Exception {
- File homeDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ClusterProperties.CLUSTER_NAME, "sonarqube");
- props.set(ClusterProperties.CLUSTER_ENABLED, "true");
- 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
- EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
- Map<String, String> generated = esSettings.build();
- assertThat(generated.get("node.name")).startsWith("sonarqube-");
- }
-
- @Test
- public void test_node_name_default_for_standalone_mode() throws Exception {
- File homeDir = temp.newFolder();
- Props props = new Props(new Properties());
- props.set(ClusterProperties.CLUSTER_NAME, "sonarqube");
- props.set(ClusterProperties.CLUSTER_ENABLED, "false");
- 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.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
- EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
- Map<String, String> generated = esSettings.build();
- assertThat(generated.get("node.name")).isEqualTo("sonarqube");
- }
-
- @Test
- public void path_properties_are_values_from_EsFileSystem_argument() throws IOException {
- EsFileSystem mockedEsFileSystem = mock(EsFileSystem.class);
- when(mockedEsFileSystem.getHomeDirectory()).thenReturn(new File("/foo/home"));
- when(mockedEsFileSystem.getConfDirectory()).thenReturn(new File("/foo/conf"));
- when(mockedEsFileSystem.getLogDirectory()).thenReturn(new File("/foo/log"));
- when(mockedEsFileSystem.getDataDirectory()).thenReturn(new File("/foo/data"));
-
- EsSettings underTest = new EsSettings(minProps(new Random().nextBoolean()), mockedEsFileSystem, System2.INSTANCE);
-
- Map<String, String> generated = underTest.build();
- assertThat(generated.get("path.data")).isEqualTo("/foo/data");
- assertThat(generated.get("path.logs")).isEqualTo("/foo/log");
- assertThat(generated.get("path.conf")).isEqualTo("/foo/conf");
- }
-
- @Test
- public void set_discovery_settings_if_cluster_is_enabled() throws Exception {
- Props props = minProps(CLUSTER_ENABLED);
- props.set(CLUSTER_SEARCH_HOSTS, "1.2.3.4:9000,1.2.3.5:8080");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
-
- 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(CLUSTER_ENABLED);
- props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "ꝱꝲꝳପ");
-
- EsSettings underTest = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
-
- 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(CLUSTER_ENABLED);
- props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).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(CLUSTER_ENABLED);
- props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
-
- assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("10s");
- }
-
- @Test
- public void in_standalone_initialTimeout_is_not_overridable() throws Exception {
- Props props = minProps(CLUSTER_DISABLED);
- props.set(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "10s");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
-
- assertThat(settings.get("discovery.initial_state_timeout")).isEqualTo("30s");
- }
-
- @Test
- public void in_standalone_minimumMasterNodes_is_not_overridable() throws Exception {
- Props props = minProps(CLUSTER_DISABLED);
- props.set(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, "5");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).build();
-
- assertThat(settings.get("discovery.zen.minimum_master_nodes")).isEqualTo("1");
- }
-
- @Test
- public void enable_http_connector() throws Exception {
- Props props = minProps(CLUSTER_DISABLED);
- props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).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(CLUSTER_DISABLED);
- props.set(ProcessProperties.SEARCH_HTTP_PORT, "9010");
- props.set(ProcessProperties.SEARCH_HOST, "127.0.0.2");
- Map<String, String> settings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE).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(ClusterProperties.CLUSTER_ENABLED, Boolean.toString(cluster));
- return props;
- }
-}
+++ /dev/null
-package org.sonar.process.es;/*
- * 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.
- */
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class EsYmlSettingsTest {
-
- @Rule
- public TemporaryFolder folder= new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void test_generation_of_file() throws IOException {
- File yamlFile = folder.newFile();
- new EsYmlSettings(new HashMap<>()).writeToYmlSettingsFile(yamlFile);
-
- assertThat(yamlFile).exists();
- assertThat(yamlFile).hasContent("# This file has been automatically generated by SonarQube during startup.\n" +
- "\n" +
- "# DO NOT EDIT THIS FILE\n" +
- "\n" +
- "{\n" +
- " }");
- }
-
- @Test
- public void if_file_is_not_writable_ISE_must_be_thrown() throws IOException {
- File yamlFile = folder.newFile();
- yamlFile.setReadOnly();
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Cannot write Elasticsearch yml settings file");
-
- new EsYmlSettings(new HashMap<>()).writeToYmlSettingsFile(yamlFile);
- }
-}
+++ /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.process.jmvoptions;
-
-import java.io.File;
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class CeJvmOptionsTest {
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- @Test
- public void constructor_sets_mandatory_JVM_options() throws IOException {
- File tmpDir = temporaryFolder.newFolder();
- CeJvmOptions underTest = new CeJvmOptions(tmpDir);
-
- assertThat(underTest.getAll()).containsExactly(
- "-Djava.awt.headless=true", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=" + tmpDir.getAbsolutePath());
- }
-}
+++ /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.process.jmvoptions;
-
-import java.io.File;
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.test.ExceptionCauseMatcher.hasType;
-
-public class EsJvmOptionsTest {
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void constructor_sets_mandatory_JVM_options() {
- EsJvmOptions underTest = new EsJvmOptions();
-
- assertThat(underTest.getAll()).containsExactly(
- "-XX:+UseConcMarkSweepGC",
- "-XX:CMSInitiatingOccupancyFraction=75",
- "-XX:+UseCMSInitiatingOccupancyOnly",
- "-XX:+AlwaysPreTouch",
- "-server",
- "-Xss1m",
- "-Djava.awt.headless=true",
- "-Dfile.encoding=UTF-8",
- "-Djna.nosys=true",
- "-Djdk.io.permissionsUseCanonicalPath=true",
- "-Dio.netty.noUnsafe=true",
- "-Dio.netty.noKeySetOptimization=true",
- "-Dio.netty.recycler.maxCapacityPerThread=0",
- "-Dlog4j.shutdownHookEnabled=false",
- "-Dlog4j2.disable.jmx=true",
- "-Dlog4j.skipJansi=true");
- }
-
- @Test
- public void writeToJvmOptionFile_writes_all_JVM_options_to_file_with_warning_header() throws IOException {
- File file = temporaryFolder.newFile();
- EsJvmOptions underTest = new EsJvmOptions()
- .add("-foo")
- .add("-bar");
-
- underTest.writeToJvmOptionFile(file);
-
- assertThat(file).hasContent(
- "# This file has been automatically generated by SonarQube during startup.\n" +
- "# Please use sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
- "\n" +
- "# DO NOT EDIT THIS FILE\n" +
- "\n" +
- "-XX:+UseConcMarkSweepGC\n" +
- "-XX:CMSInitiatingOccupancyFraction=75\n" +
- "-XX:+UseCMSInitiatingOccupancyOnly\n" +
- "-XX:+AlwaysPreTouch\n" +
- "-server\n" +
- "-Xss1m\n" +
- "-Djava.awt.headless=true\n" +
- "-Dfile.encoding=UTF-8\n" +
- "-Djna.nosys=true\n" +
- "-Djdk.io.permissionsUseCanonicalPath=true\n" +
- "-Dio.netty.noUnsafe=true\n" +
- "-Dio.netty.noKeySetOptimization=true\n" +
- "-Dio.netty.recycler.maxCapacityPerThread=0\n" +
- "-Dlog4j.shutdownHookEnabled=false\n" +
- "-Dlog4j2.disable.jmx=true\n" +
- "-Dlog4j.skipJansi=true\n" +
- "-foo\n" +
- "-bar");
-
- }
-
- @Test
- public void writeToJvmOptionFile_throws_ISE_in_case_of_IOException() throws IOException {
- File notAFile = temporaryFolder.newFolder();
- EsJvmOptions underTest = new EsJvmOptions();
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Cannot write Elasticsearch jvm options file");
- expectedException.expectCause(hasType(IOException.class));
-
- underTest.writeToJvmOptionFile(notAFile);
- }
-}
+++ /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.process.jmvoptions;
-
-import com.google.common.collect.ImmutableMap;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Random;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.sonar.process.MessageException;
-import org.sonar.process.Props;
-
-import static java.lang.String.valueOf;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
-
-@RunWith(DataProviderRunner.class)
-public class JvmOptionsTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private final Random random = new Random();
- private final String randomPropertyName = randomAlphanumeric(3);
- private final String randomPrefix = "-" + randomAlphabetic(5).toLowerCase(Locale.ENGLISH);
- private final String randomValue = randomAlphanumeric(4).toLowerCase(Locale.ENGLISH);
- private final Properties properties = new Properties();
- private final JvmOptions underTest = new JvmOptions();
-
- @Test
- public void constructor_without_arguments_creates_empty_JvmOptions() {
- JvmOptions<JvmOptions> testJvmOptions = new JvmOptions<>();
-
- assertThat(testJvmOptions.getAll()).isEmpty();
- }
-
- @Test
- public void constructor_throws_NPE_if_argument_is_null() {
- expectJvmOptionNotNullNPE();
-
- new JvmOptions(null);
- }
-
- @Test
- public void constructor_throws_NPE_if_any_option_prefix_is_null() {
- Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
- Stream.of(
- IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
- Stream.of(new Option(null, "value")))
- .flatMap(s -> s));
-
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("JVM option prefix can't be null");
-
- new JvmOptions(mandatoryJvmOptions);
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void constructor_throws_IAE_if_any_option_prefix_is_empty(String emptyString) {
- Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
- Stream.of(
- IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
- Stream.of(new Option(emptyString, "value")))
- .flatMap(s -> s));
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("JVM option prefix can't be empty");
-
- new JvmOptions(mandatoryJvmOptions);
- }
-
- @Test
- public void constructor_throws_IAE_if_any_option_prefix_does_not_start_with_dash() {
- String invalidPrefix = randomAlphanumeric(3);
- Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
- Stream.of(
- IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
- Stream.of(new Option(invalidPrefix, "value")))
- .flatMap(s -> s));
-
- expectJvmOptionNotEmptyAndStartByDashIAE();
-
- new JvmOptions(mandatoryJvmOptions);
- }
-
- @Test
- public void constructor_throws_NPE_if_any_option_value_is_null() {
- Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
- Stream.of(
- IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
- Stream.of(new Option("-prefix", null)))
- .flatMap(s -> s));
-
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("JVM option value can't be null");
-
- new JvmOptions(mandatoryJvmOptions);
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void constructor_accepts_any_empty_option_value(String emptyString) {
- Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
- Stream.of(
- IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
- Stream.of(new Option("-prefix", emptyString)))
- .flatMap(s -> s));
-
- new JvmOptions(mandatoryJvmOptions);
- }
-
- @Test
- public void add_throws_NPE_if_argument_is_null() {
- expectJvmOptionNotNullNPE();
-
- underTest.add(null);
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void add_throws_IAE_if_argument_is_empty(String emptyString) {
- expectJvmOptionNotEmptyAndStartByDashIAE();
-
- underTest.add(emptyString);
- }
-
- @Test
- public void add_throws_IAE_if_argument_does_not_start_with_dash() {
- expectJvmOptionNotEmptyAndStartByDashIAE();
-
- underTest.add(randomAlphanumeric(3));
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void add_adds_with_trimming(String emptyString) {
- underTest.add(emptyString + "-foo" + emptyString);
-
- assertThat(underTest.getAll()).containsOnly("-foo");
- }
-
- @Test
- public void add_throws_MessageException_if_option_starts_with_prefix_of_mandatory_option_but_has_different_value() {
- String[] optionOverrides = {
- randomPrefix,
- randomPrefix + randomAlphanumeric(1),
- randomPrefix + randomAlphanumeric(2),
- randomPrefix + randomAlphanumeric(3),
- randomPrefix + randomAlphanumeric(4),
- randomPrefix + randomValue.substring(1),
- randomPrefix + randomValue.substring(2),
- randomPrefix + randomValue.substring(3)
- };
-
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- for (String optionOverride : optionOverrides) {
- try {
- underTest.add(optionOverride);
- fail("an MessageException should have been thrown");
- } catch (MessageException e) {
- assertThat(e.getMessage()).isEqualTo("a JVM option can't overwrite mandatory JVM options. " + optionOverride + " overwrites " + randomPrefix + randomValue);
- }
- }
- }
-
- @Test
- public void add_checks_against_mandatory_options_is_case_sensitive() {
- String[] optionOverrides = {
- randomPrefix,
- randomPrefix + randomAlphanumeric(1),
- randomPrefix + randomAlphanumeric(2),
- randomPrefix + randomAlphanumeric(3),
- randomPrefix + randomAlphanumeric(4),
- randomPrefix + randomValue.substring(1),
- randomPrefix + randomValue.substring(2),
- randomPrefix + randomValue.substring(3)
- };
-
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- for (String optionOverride : optionOverrides) {
- underTest.add(optionOverride.toUpperCase(Locale.ENGLISH));
- }
- }
-
- @Test
- public void add_accepts_property_equal_to_mandatory_option_and_does_not_add_it_twice() {
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- underTest.add(randomPrefix + randomValue);
-
- assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
- }
-
- @Test
- public void addFromMandatoryProperty_fails_with_IAE_if_property_does_not_exist() {
- expectMissingPropertyIAE(this.randomPropertyName);
-
- underTest.addFromMandatoryProperty(new Props(properties), this.randomPropertyName);
- }
-
- @Test
- public void addFromMandatoryProperty_fails_with_IAE_if_property_contains_an_empty_value() {
- expectMissingPropertyIAE(this.randomPropertyName);
-
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void addFromMandatoryProperty_adds_single_option_of_property_with_trimming(String emptyString) {
- properties.put(randomPropertyName, emptyString + "-foo" + emptyString);
-
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
-
- assertThat(underTest.getAll()).containsOnly("-foo");
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void addFromMandatoryProperty_fails_with_MessageException_if_property_does_not_start_with_dash_after_trimmed(String emptyString) {
- properties.put(randomPropertyName, emptyString + "foo -bar");
-
- expectJvmOptionNotEmptyAndStartByDashMessageException(randomPropertyName, "foo");
-
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
- }
-
- @Test
- @UseDataProvider("variousEmptyStrings")
- public void addFromMandatoryProperty_adds_options_of_property_with_trimming(String emptyString) {
- properties.put(randomPropertyName, emptyString + "-foo" + emptyString + " -bar" + emptyString + " -duck" + emptyString);
-
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
-
- assertThat(underTest.getAll()).containsOnly("-foo", "-bar", "-duck");
- }
-
- @Test
- public void addFromMandatoryProperty_supports_spaces_inside_options() {
- properties.put(randomPropertyName, "-foo bar -duck");
-
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
-
- assertThat(underTest.getAll()).containsOnly("-foo bar", "-duck");
- }
-
- @Test
- public void addFromMandatoryProperty_throws_IAE_if_option_starts_with_prefix_of_mandatory_option_but_has_different_value() {
- String[] optionOverrides = {
- randomPrefix,
- randomPrefix + randomValue.substring(1),
- randomPrefix + randomValue.substring(1),
- randomPrefix + randomValue.substring(2),
- randomPrefix + randomValue.substring(3),
- randomPrefix + randomValue.substring(3) + randomAlphanumeric(1),
- randomPrefix + randomValue.substring(3) + randomAlphanumeric(2),
- randomPrefix + randomValue.substring(3) + randomAlphanumeric(3),
- randomPrefix + randomValue + randomAlphanumeric(1)
- };
-
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- for (String optionOverride : optionOverrides) {
- try {
- properties.put(randomPropertyName, optionOverride);
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
- fail("an MessageException should have been thrown");
- } catch (MessageException e) {
- assertThat(e.getMessage())
- .isEqualTo("a JVM option can't overwrite mandatory JVM options. " +
- "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " + optionOverride + " overwrites " + randomPrefix + randomValue);
- }
- }
- }
-
- @Test
- public void addFromMandatoryProperty_checks_against_mandatory_options_is_case_sensitive() {
- String[] optionOverrides = {
- randomPrefix,
- randomPrefix + randomValue.substring(1),
- randomPrefix + randomValue.substring(1),
- randomPrefix + randomValue.substring(2),
- randomPrefix + randomValue.substring(3),
- randomPrefix + randomValue.substring(3) + randomAlphanumeric(1),
- randomPrefix + randomValue.substring(3) + randomAlphanumeric(2),
- randomPrefix + randomValue.substring(3) + randomAlphanumeric(3),
- randomPrefix + randomValue + randomAlphanumeric(1)
- };
-
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- for (String optionOverride : optionOverrides) {
- properties.setProperty(randomPropertyName, optionOverride.toUpperCase(Locale.ENGLISH));
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
- }
- }
-
- @Test
- public void addFromMandatoryProperty_reports_all_overriding_options_in_single_exception() {
- String overriding1 = randomPrefix;
- String overriding2 = randomPrefix + randomValue + randomAlphanumeric(1);
- properties.setProperty(randomPropertyName, "-foo " + overriding1 + " -bar " + overriding2);
-
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("a JVM option can't overwrite mandatory JVM options. " +
- "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " +
- overriding1 + " overwrites " + randomPrefix + randomValue + ", " + overriding2 + " overwrites " + randomPrefix + randomValue);
-
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
- }
-
- @Test
- public void addFromMandatoryProperty_accepts_property_equal_to_mandatory_option_and_does_not_add_it_twice() {
- JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
-
- properties.put(randomPropertyName, randomPrefix + randomValue);
- underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
-
- assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
- }
-
- @Test
- public void toString_prints_all_jvm_options() {
- underTest.add("-foo").add("-bar");
-
- assertThat(underTest.toString()).isEqualTo("[-foo, -bar]");
- }
-
- private void expectJvmOptionNotNullNPE() {
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("a JVM option can't be null");
- }
-
- private void expectJvmOptionNotEmptyAndStartByDashIAE() {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("a JVM option can't be empty and must start with '-'");
- }
-
- private void expectJvmOptionNotEmptyAndStartByDashMessageException(String randomPropertyName, String option) {
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("a JVM option can't be empty and must start with '-'. " +
- "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " + option);
- }
-
- public void expectMissingPropertyIAE(String randomPropertyName) {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Missing property: " + randomPropertyName);
- }
-
- @DataProvider()
- public static Object[][] variousEmptyStrings() {
- return new Object[][] {
- {""},
- {" "},
- {" "}
- };
- }
-
- private static Map<String, String> shuffleThenToMap(Stream<Option> stream) {
- List<Option> options = stream.collect(Collectors.toList());
- Collections.shuffle(options);
- Map<String, String> res = new HashMap<>(options.size());
- for (Option option : options) {
- res.put(option.getPrefix(), option.getValue());
- }
- return res;
- }
-
- private static final class Option {
- private final String prefix;
- private final String value;
-
- private Option(String prefix, String value) {
- this.prefix = prefix;
- this.value = value;
- }
-
- public String getPrefix() {
- return prefix;
- }
-
- public String getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return "[" + prefix + "-" + value + ']';
- }
- }
-}
+++ /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.process.jmvoptions;
-
-import java.io.File;
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class WebJvmOptionsTest {
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- @Test
- public void constructor_sets_mandatory_JVM_options() throws IOException {
- File tmpDir = temporaryFolder.newFolder();
- WebJvmOptions underTest = new WebJvmOptions(tmpDir);
-
- assertThat(underTest.getAll()).containsExactly(
- "-Djava.awt.headless=true", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=" + tmpDir.getAbsolutePath());
- }
-
-}
+++ /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.process.logging;
-
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.AppenderBase;
-import java.util.ArrayList;
-import java.util.List;
-
-public final class ListAppender extends AppenderBase<ILoggingEvent> {
- private final List<ILoggingEvent> logs = new ArrayList<>();
-
- @Override
- protected void append(ILoggingEvent eventObject) {
- logs.add(eventObject);
- }
-
- public List<ILoggingEvent> getLogs() {
- return logs;
- }
-
- public static <T> ListAppender attachMemoryAppenderToLoggerOf(Class<T> loggerClass) {
- ListAppender listAppender = new ListAppender();
- new LogbackHelper().getRootContext().getLogger(loggerClass)
- .addAppender(listAppender);
- listAppender.start();
- return listAppender;
- }
-
- public static <T> void detachMemoryAppenderToLoggerOf(Class<T> loggerClass, ListAppender listAppender) {
- listAppender.stop();
- new LogbackHelper().getRootContext().getLogger(loggerClass)
- .detachAppender(listAppender);
- }
-}
import org.sonar.application.process.StopRequestWatcherImpl;
import org.sonar.process.System2;
import org.sonar.process.SystemExit;
-import org.sonar.process.command.CommandFactory;
-import org.sonar.process.command.CommandFactoryImpl;
+import org.sonar.application.command.CommandFactory;
+import org.sonar.application.command.CommandFactoryImpl;
import static org.sonar.application.config.SonarQubeVersionHelper.getSonarqubeVersion;
import static org.sonar.cluster.ClusterProperties.CLUSTER_NAME;