]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9803 restrict sonar-process to classes shared by all processes only
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 14 Sep 2017 14:33:26 +0000 (16:33 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 26 Sep 2017 21:49:37 +0000 (23:49 +0200)
64 files changed:
server/sonar-main/pom.xml
server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java
server/sonar-main/src/main/java/org/sonar/application/command/AbstractCommand.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/CeJvmOptions.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/CommandFactory.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/CommandFactoryImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/EsCommand.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/EsJvmOptions.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/JavaCommand.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/JvmOptions.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/WebJvmOptions.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/command/package-info.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/es/EsFileSystem.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/es/EsSettings.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/es/EsYmlSettings.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/es/package-info.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java
server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncher.java
server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java
server/sonar-main/src/test/java/org/sonar/application/command/AbstractCommandTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/command/CeJvmOptionsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/command/CommandFactoryImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/command/EsJvmOptionsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/command/JavaCommandTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/command/JvmOptionsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/command/WebJvmOptionsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/es/EsFileSystemTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/es/EsLoggingTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/es/EsSettingsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/es/EsYmlSettingsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/logging/ListAppender.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/EsProcessMonitorTest.java
server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
server/sonar-process/src/main/java/org/sonar/process/command/AbstractCommand.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/command/CommandFactory.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/command/CommandFactoryImpl.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/command/EsCommand.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/command/JavaCommand.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/command/package-info.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/es/EsFileSystem.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/es/EsLogging.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/es/EsSettings.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/es/EsYmlSettings.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/es/package-info.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/CeJvmOptions.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/EsJvmOptions.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/JvmOptions.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/WebJvmOptions.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/package-info.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/command/AbstractCommandTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/command/CommandFactoryImplTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/command/JavaCommandTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/es/EsFileSystemTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/es/EsLoggingTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/es/EsSettingsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/es/EsYmlSettingsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/CeJvmOptionsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/EsJvmOptionsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/JvmOptionsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/WebJvmOptionsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/logging/ListAppender.java [deleted file]
sonar-application/src/main/java/org/sonar/application/App.java

index 9aa79852803e9b71bacaeed5af3ccb121d5eeed0..707e0fb601635646ccd47d958f36fc7331be5508 100644 (file)
       <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>
index 3268bb249c6cae9f63001836873b00ba711a7e3a..0f9214e20b1817c6e87f38d2df709e37c83f56a9 100644 (file)
@@ -43,9 +43,9 @@ import org.sonar.application.process.ProcessLifecycleListener;
 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 {
 
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/AbstractCommand.java b/server/sonar-main/src/main/java/org/sonar/application/command/AbstractCommand.java
new file mode 100644 (file)
index 0000000..2da97bb
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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();
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/CeJvmOptions.java b/server/sonar-main/src/main/java/org/sonar/application/command/CeJvmOptions.java
new file mode 100644 (file)
index 0000000..57e4b1b
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/CommandFactory.java b/server/sonar-main/src/main/java/org/sonar/application/command/CommandFactory.java
new file mode 100644 (file)
index 0000000..e55f364
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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();
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/CommandFactoryImpl.java b/server/sonar-main/src/main/java/org/sonar/application/command/CommandFactoryImpl.java
new file mode 100644 (file)
index 0000000..0df586a
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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));
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/EsCommand.java b/server/sonar-main/src/main/java/org/sonar/application/command/EsCommand.java
new file mode 100644 (file)
index 0000000..d44e272
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/EsJvmOptions.java b/server/sonar-main/src/main/java/org/sonar/application/command/EsJvmOptions.java
new file mode 100644 (file)
index 0000000..bf2dfd6
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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);
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/JavaCommand.java b/server/sonar-main/src/main/java/org/sonar/application/command/JavaCommand.java
new file mode 100644 (file)
index 0000000..5cee5a9
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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() +
+      '}';
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/JvmOptions.java b/server/sonar-main/src/main/java/org/sonar/application/command/JvmOptions.java
new file mode 100644 (file)
index 0000000..3ef1b3b
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * 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;
+    }
+
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/WebJvmOptions.java b/server/sonar-main/src/main/java/org/sonar/application/command/WebJvmOptions.java
new file mode 100644 (file)
index 0000000..9a066a7
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/command/package-info.java b/server/sonar-main/src/main/java/org/sonar/application/command/package-info.java
new file mode 100644 (file)
index 0000000..a083f0a
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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;
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsFileSystem.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsFileSystem.java
new file mode 100644 (file)
index 0000000..73fc79e
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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");
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsLogging.java
new file mode 100644 (file)
index 0000000..90fb1d7
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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();
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsSettings.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsSettings.java
new file mode 100644 (file)
index 0000000..eb69acd
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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));
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/EsYmlSettings.java b/server/sonar-main/src/main/java/org/sonar/application/es/EsYmlSettings.java
new file mode 100644 (file)
index 0000000..3ba92d5
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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);
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/es/package-info.java b/server/sonar-main/src/main/java/org/sonar/application/es/package-info.java
new file mode 100644 (file)
index 0000000..8f29605
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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;
index e4730c7303d4bd766942fe69f22f3486598faec5..fd611188f953a5ee848980fbaae2585a82427537 100644 (file)
@@ -38,7 +38,7 @@ import org.elasticsearch.discovery.MasterNotDiscoveredException;
 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;
index c39f91bc8fa75d9696fc4e3ab455db85fde31517..c0ca8ec7825976a69ac3d529173929a52b283b7c 100644 (file)
@@ -20,8 +20,8 @@
 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 {
 
index b0b968f05eb2cb491945d39296d4f7aa39787a7a..1a8daf01ae359c25f53eb816131fa59579798e22 100644 (file)
@@ -31,12 +31,12 @@ import java.util.Properties;
 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;
 
index f419f4d0e171563e066d7c3d1c473fcab1258073..a2ada4bb3f87bbe6e2fbc8eb627b6298391464cb 100644 (file)
@@ -43,10 +43,10 @@ import org.sonar.application.process.ProcessLauncher;
 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;
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/AbstractCommandTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/AbstractCommandTest.java
new file mode 100644 (file)
index 0000000..c5ff76b
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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);
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/CeJvmOptionsTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/CeJvmOptionsTest.java
new file mode 100644 (file)
index 0000000..4084336
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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());
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/CommandFactoryImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/CommandFactoryImplTest.java
new file mode 100644 (file)
index 0000000..ceb13a0
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * 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);
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/EsJvmOptionsTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/EsJvmOptionsTest.java
new file mode 100644 (file)
index 0000000..9f4aa5e
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/JavaCommandTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/JavaCommandTest.java
new file mode 100644 (file)
index 0000000..bd39967
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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);
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/JvmOptionsTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/JvmOptionsTest.java
new file mode 100644 (file)
index 0000000..0385069
--- /dev/null
@@ -0,0 +1,427 @@
+/*
+ * 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 + ']';
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/command/WebJvmOptionsTest.java b/server/sonar-main/src/test/java/org/sonar/application/command/WebJvmOptionsTest.java
new file mode 100644 (file)
index 0000000..dfd8f15
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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());
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/es/EsFileSystemTest.java b/server/sonar-main/src/test/java/org/sonar/application/es/EsFileSystemTest.java
new file mode 100644 (file)
index 0000000..e015b6b
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * 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"));
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/es/EsLoggingTest.java b/server/sonar-main/src/test/java/org/sonar/application/es/EsLoggingTest.java
new file mode 100644 (file)
index 0000000..7dc29ec
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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());
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/es/EsSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/es/EsSettingsTest.java
new file mode 100644 (file)
index 0000000..1a09f0a
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * 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;
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/es/EsYmlSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/es/EsYmlSettingsTest.java
new file mode 100644 (file)
index 0000000..6920c7f
--- /dev/null
@@ -0,0 +1,62 @@
+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);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/logging/ListAppender.java b/server/sonar-main/src/test/java/org/sonar/application/logging/ListAppender.java
new file mode 100644 (file)
index 0000000..7a8ca8b
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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);
+  }
+}
index d0ef83444fc3eec690e31c7260ff9e75d885338f..7af20a08cc1e405b3303a9dfbf324481f9d54fd6 100644 (file)
@@ -35,7 +35,7 @@ import org.elasticsearch.discovery.MasterNotDiscoveredException;
 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;
index a0a4bba391dc65a7b75660dcc1a92debedc6ff2e..36a288c1f028c7c2965360254c8b1b00d59e0c4d 100644 (file)
@@ -31,8 +31,8 @@ import org.junit.Test;
 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;
diff --git a/server/sonar-process/src/main/java/org/sonar/process/command/AbstractCommand.java b/server/sonar-process/src/main/java/org/sonar/process/command/AbstractCommand.java
deleted file mode 100644 (file)
index d968b8a..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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();
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/command/CommandFactory.java b/server/sonar-process/src/main/java/org/sonar/process/command/CommandFactory.java
deleted file mode 100644 (file)
index f30537e..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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();
-
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/command/CommandFactoryImpl.java b/server/sonar-process/src/main/java/org/sonar/process/command/CommandFactoryImpl.java
deleted file mode 100644 (file)
index b6d8ded..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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));
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/command/EsCommand.java b/server/sonar-process/src/main/java/org/sonar/process/command/EsCommand.java
deleted file mode 100644 (file)
index a6d59ac..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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;
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/command/JavaCommand.java b/server/sonar-process/src/main/java/org/sonar/process/command/JavaCommand.java
deleted file mode 100644 (file)
index fd461b2..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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() +
-      '}';
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/command/package-info.java b/server/sonar-process/src/main/java/org/sonar/process/command/package-info.java
deleted file mode 100644 (file)
index 248864d..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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;
diff --git a/server/sonar-process/src/main/java/org/sonar/process/es/EsFileSystem.java b/server/sonar-process/src/main/java/org/sonar/process/es/EsFileSystem.java
deleted file mode 100644 (file)
index 033791c..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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");
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/es/EsLogging.java b/server/sonar-process/src/main/java/org/sonar/process/es/EsLogging.java
deleted file mode 100644 (file)
index b5a6727..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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();
-  }
-
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/es/EsSettings.java b/server/sonar-process/src/main/java/org/sonar/process/es/EsSettings.java
deleted file mode 100644 (file)
index 42eac51..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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));
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/es/EsYmlSettings.java b/server/sonar-process/src/main/java/org/sonar/process/es/EsYmlSettings.java
deleted file mode 100644 (file)
index 6eae2b1..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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);
-    }
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/es/package-info.java b/server/sonar-process/src/main/java/org/sonar/process/es/package-info.java
deleted file mode 100644 (file)
index 35e9b4a..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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;
diff --git a/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/CeJvmOptions.java b/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/CeJvmOptions.java
deleted file mode 100644 (file)
index b9b4ff0..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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;
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/EsJvmOptions.java b/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/EsJvmOptions.java
deleted file mode 100644 (file)
index eddf98b..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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);
-    }
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/JvmOptions.java b/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/JvmOptions.java
deleted file mode 100644 (file)
index 13560f9..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * 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;
-    }
-
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/WebJvmOptions.java b/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/WebJvmOptions.java
deleted file mode 100644 (file)
index a2a3f8f..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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;
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/package-info.java b/server/sonar-process/src/main/java/org/sonar/process/jmvoptions/package-info.java
deleted file mode 100644 (file)
index f88e33e..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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;
diff --git a/server/sonar-process/src/test/java/org/sonar/process/command/AbstractCommandTest.java b/server/sonar-process/src/test/java/org/sonar/process/command/AbstractCommandTest.java
deleted file mode 100644 (file)
index dec2dde..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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);
-  }
-
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/command/CommandFactoryImplTest.java b/server/sonar-process/src/test/java/org/sonar/process/command/CommandFactoryImplTest.java
deleted file mode 100644 (file)
index c6b2abc..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * 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);
-  }
-
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/command/JavaCommandTest.java b/server/sonar-process/src/test/java/org/sonar/process/command/JavaCommandTest.java
deleted file mode 100644 (file)
index dd0f71a..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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);
-  }
-
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/es/EsFileSystemTest.java b/server/sonar-process/src/test/java/org/sonar/process/es/EsFileSystemTest.java
deleted file mode 100644 (file)
index d3d1069..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * 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"));
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/es/EsLoggingTest.java b/server/sonar-process/src/test/java/org/sonar/process/es/EsLoggingTest.java
deleted file mode 100644 (file)
index 4f511ef..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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());
-    }
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/es/EsSettingsTest.java b/server/sonar-process/src/test/java/org/sonar/process/es/EsSettingsTest.java
deleted file mode 100644 (file)
index d8bc647..0000000
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * 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;
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/es/EsYmlSettingsTest.java b/server/sonar-process/src/test/java/org/sonar/process/es/EsYmlSettingsTest.java
deleted file mode 100644 (file)
index 0fd6dc8..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-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);
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/CeJvmOptionsTest.java b/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/CeJvmOptionsTest.java
deleted file mode 100644 (file)
index 3bd8bf3..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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());
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/EsJvmOptionsTest.java b/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/EsJvmOptionsTest.java
deleted file mode 100644 (file)
index dd47b11..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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);
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/JvmOptionsTest.java b/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/JvmOptionsTest.java
deleted file mode 100644 (file)
index 35de8ef..0000000
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * 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 + ']';
-    }
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/WebJvmOptionsTest.java b/server/sonar-process/src/test/java/org/sonar/process/jmvoptions/WebJvmOptionsTest.java
deleted file mode 100644 (file)
index 9387a15..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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());
-  }
-
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/ListAppender.java b/server/sonar-process/src/test/java/org/sonar/process/logging/ListAppender.java
deleted file mode 100644 (file)
index fec8007..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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);
-  }
-}
index 54d233f5947ed7710f626e6dd53eaec2cdadeb5e..1060726970a21e8332bd616f27eef03684e58a99 100644 (file)
@@ -29,8 +29,8 @@ import org.sonar.application.process.StopRequestWatcher;
 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;