]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9590 only heap and heap dump options are now in search.javaOpts
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 18 Aug 2017 14:11:54 +0000 (16:11 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 5 Sep 2017 12:24:12 +0000 (14:24 +0200)
27 files changed:
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java
server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
server/sonar-process/src/main/java/org/sonar/process/command/AbstractCommand.java
server/sonar-process/src/main/java/org/sonar/process/command/CommandFactoryImpl.java
server/sonar-process/src/main/java/org/sonar/process/command/EsCommand.java
server/sonar-process/src/main/java/org/sonar/process/command/JavaCommand.java
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/CeJvmOptions.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/EsJvmOptions.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/JvmOptions.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/WebJvmOptions.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/jmvoptions/package-info.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/command/AbstractCommandTest.java
server/sonar-process/src/test/java/org/sonar/process/command/JavaCommandTest.java
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/CeJvmOptionsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/EsJvmOptionsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/JvmOptionsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/WebJvmOptionsTest.java [new file with mode: 0644]
sonar-application/src/main/assembly/conf/sonar.properties
sonar-application/src/main/java/org/sonar/application/App.java
sonar-testing-harness/src/main/java/org/sonar/test/ExceptionCauseMatcher.java
tests/src/test/java/org/sonarqube/tests/Category1Suite.java
tests/src/test/java/org/sonarqube/tests/Category2Suite.java
tests/src/test/java/org/sonarqube/tests/Category3Suite.java
tests/src/test/java/org/sonarqube/tests/Category4Suite.java
tests/src/test/java/org/sonarqube/tests/Category6Suite.java

index bc19b2a2a6971d6d1fb2e3ce692fb3d690d7d947..549e4ddfe4eda9e18fe168248d8e47c7232788b3 100644 (file)
@@ -23,15 +23,12 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,6 +36,7 @@ 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.jmvoptions.JvmOptions;
 import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
 import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
@@ -50,11 +48,6 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;
 
 public class ProcessLauncherImpl implements ProcessLauncher {
   private static final Logger LOG = LoggerFactory.getLogger(ProcessLauncherImpl.class);
-  private static final String ELASTICSEARCH_JVM_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
-    "# Please use the sonar.search.javaOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
-    "\n" +
-    "# DO NOT EDIT THIS FILE\n" +
-    "\n";
 
   private final File tempDir;
   private final AllProcessesCommands allProcessesCommands;
@@ -106,7 +99,7 @@ public class ProcessLauncherImpl implements ProcessLauncher {
 
     try {
       IOUtils.copy(getClass().getResourceAsStream("elasticsearch.yml"), new FileOutputStream(new File(confDir, "elasticsearch.yml")));
-      writeJvmOptions(esCommand, new File(confDir, "jvm.options"));
+      esCommand.getEsJvmOptions().writeToJvmOptionFile(new File(confDir, "jvm.options"));
       esCommand.getLog4j2Properties().store(new FileOutputStream(new File(confDir, "log4j2.properties")), "log42 properties file for ES bundled in SonarQube");
     } catch (IOException e) {
       throw new IllegalStateException("Failed to write ES configuration files", e);
@@ -150,29 +143,10 @@ public class ProcessLauncherImpl implements ProcessLauncher {
     return create(esCommand, commands);
   }
 
-  private static void writeJvmOptions(EsCommand esCommand, File jvmOptionsFile) {
-    String jvmOptions = esCommand.getJvmOptions()
-      .stream()
-
-      // we do not expect the user to use parameters containing " -"
-      .map(s -> s.split(" (?=-)"))
-      .flatMap(Arrays::stream)
-      .collect(Collectors.joining("\n"));
-    String jvmOptionsContent = ELASTICSEARCH_JVM_OPTIONS_HEADER + jvmOptions;
-    try {
-      Files.write(jvmOptionsFile.toPath(), jvmOptionsContent.getBytes(Charset.forName("UTF-8")));
-    } catch (IOException e) {
-      throw new IllegalStateException("Cannot write Elasticsearch jvm options file", e);
-    }
-  }
-
-  private ProcessBuilder create(JavaCommand javaCommand) {
+  private <T extends JvmOptions> ProcessBuilder create(JavaCommand<T> javaCommand) {
     List<String> commands = new ArrayList<>();
     commands.add(buildJavaPath());
-    commands.addAll(javaCommand.getJavaOptions());
-    // TODO warning - does it work if temp dir contains a whitespace ?
-    // TODO move to CommandFactory ?
-    commands.add(format("-Djava.io.tmpdir=%s", tempDir.getAbsolutePath()));
+    commands.addAll(javaCommand.getJvmOptions().getAll());
     commands.addAll(buildClasspath(javaCommand));
     commands.add(javaCommand.getClassName());
     commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
index 231c2627450197ac0416749759ad76a295b5c885..f7336820816ae738b79d820f06483df76f995e40 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.application;
 
+import java.io.File;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.EnumMap;
@@ -26,11 +27,13 @@ import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.DisableOnDebug;
 import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
 import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
 import org.mockito.Mockito;
@@ -56,15 +59,17 @@ import static org.sonar.process.ProcessId.WEB_SERVER;
 
 public class SchedulerImplTest {
 
-  private static final EsCommand ES_COMMAND = new EsCommand(ELASTICSEARCH);
-  private static final JavaCommand WEB_LEADER_COMMAND = new JavaCommand(WEB_SERVER);
-  private static final JavaCommand WEB_FOLLOWER_COMMAND = new JavaCommand(WEB_SERVER);
-  private static final JavaCommand CE_COMMAND = new JavaCommand(COMPUTE_ENGINE);
-
   @Rule
   public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  private EsCommand esCommand;
+  private JavaCommand webLeaderCommand;
+  private JavaCommand webFollowerCommand;
+  private JavaCommand ceCommand;
 
   private AppReloader appReloader = mock(AppReloader.class);
   private TestAppSettings settings = new TestAppSettings();
@@ -73,6 +78,15 @@ public class SchedulerImplTest {
   private TestAppState appState = new TestAppState();
   private List<ProcessId> orderedStops = synchronizedList(new ArrayList<>());
 
+  @Before
+  public void setUp() throws Exception {
+    File tempDir = temporaryFolder.newFolder();
+    esCommand = new EsCommand(ELASTICSEARCH, tempDir);
+    webLeaderCommand = new JavaCommand(WEB_SERVER, tempDir);
+    webFollowerCommand = new JavaCommand(WEB_SERVER, tempDir);
+    ceCommand = new JavaCommand(COMPUTE_ENGINE, tempDir);
+  }
+
   @After
   public void tearDown() throws Exception {
     processLauncher.close();
@@ -95,7 +109,7 @@ public class SchedulerImplTest {
     TestProcess web = processLauncher.waitForProcess(WEB_SERVER);
     assertThat(web.isAlive()).isTrue();
     assertThat(processLauncher.processes).hasSize(2);
-    assertThat(processLauncher.commands).containsExactly(ES_COMMAND, WEB_LEADER_COMMAND);
+    assertThat(processLauncher.commands).containsExactly(esCommand, webLeaderCommand);
 
     // web becomes operational -> CE is starting
     web.operational = true;
@@ -103,7 +117,7 @@ public class SchedulerImplTest {
     TestProcess ce = processLauncher.waitForProcess(COMPUTE_ENGINE);
     assertThat(ce.isAlive()).isTrue();
     assertThat(processLauncher.processes).hasSize(3);
-    assertThat(processLauncher.commands).containsExactly(ES_COMMAND, WEB_LEADER_COMMAND, CE_COMMAND);
+    assertThat(processLauncher.commands).containsExactly(esCommand, webLeaderCommand, ceCommand);
 
     // all processes are up
     processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isTrue());
@@ -307,20 +321,20 @@ public class SchedulerImplTest {
     }
   }
 
-  private static class TestCommandFactory implements CommandFactory {
+  private class TestCommandFactory implements CommandFactory {
     @Override
     public EsCommand createEsCommand() {
-      return ES_COMMAND;
+      return esCommand;
     }
 
     @Override
     public JavaCommand createWebCommand(boolean leader) {
-      return leader ? WEB_LEADER_COMMAND : WEB_FOLLOWER_COMMAND;
+      return leader ? webLeaderCommand : webFollowerCommand;
     }
 
     @Override
     public JavaCommand createCeCommand() {
-      return CE_COMMAND;
+      return ceCommand;
     }
   }
 
index 044aa7b43104e4fe20d4e8e12d2a2878da26fc7b..a0a4bba391dc65a7b75660dcc1a92debedc6ff2e 100644 (file)
@@ -30,9 +30,10 @@ import org.junit.Rule;
 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.process.sharedmemoryfile.AllProcessesCommands;
-import org.sonar.process.ProcessId;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.data.MapEntry.entry;
@@ -54,13 +55,15 @@ public class ProcessLauncherImplTest {
     File tempDir = temp.newFolder();
     TestProcessBuilder processBuilder = new TestProcessBuilder();
     ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
-    JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
+    JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, temp.newFolder());
     command.addClasspath("lib/*.class");
     command.addClasspath("lib/*.jar");
     command.setArgument("foo", "bar");
     command.setClassName("org.sonarqube.Main");
     command.setEnvVariable("VAR1", "valueOfVar1");
-    command.setWorkDir(temp.newFolder());
+    command.setJvmOptions(new JvmOptions<>()
+      .add("-Dfoo=bar")
+      .add("-Dfoo2=bar2"));
 
     ProcessMonitor monitor = underTest.launch(command);
 
@@ -68,7 +71,8 @@ public class ProcessLauncherImplTest {
     assertThat(processBuilder.started).isTrue();
     assertThat(processBuilder.commands.get(0)).endsWith("java");
     assertThat(processBuilder.commands).containsSequence(
-      "-Djava.io.tmpdir=" + tempDir.getAbsolutePath(),
+      "-Dfoo=bar",
+      "-Dfoo2=bar2",
       "-cp",
       "lib/*.class" + System.getProperty("path.separator") + "lib/*.jar",
       "org.sonarqube.Main");
@@ -84,9 +88,10 @@ public class ProcessLauncherImplTest {
     File tempDir = temp.newFolder();
     TestProcessBuilder processBuilder = new TestProcessBuilder();
     ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
-    JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
+    JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, temp.newFolder());
     command.setArgument("foo", "bar");
     command.setArgument("baz", "woo");
+    command.setJvmOptions(new JvmOptions<>());
 
     underTest.launch(command);
 
@@ -116,7 +121,7 @@ public class ProcessLauncherImplTest {
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("Fail to launch process [es]");
 
-    underTest.launch(new JavaCommand(ProcessId.ELASTICSEARCH));
+    underTest.launch(new JavaCommand(ProcessId.ELASTICSEARCH, temp.newFolder()));
   }
 
   private static class TestProcessBuilder implements ProcessLauncherImpl.ProcessBuilder {
index 63a8bc8fb17412adb261aae3c0ce6f6f83a70412..d93697144a0f6f88569745738b2a535905be293c 100644 (file)
@@ -81,10 +81,6 @@ public class ProcessProperties {
    */
   public static final String ENABLE_STOP_COMMAND = "sonar.enableStopCommand";
 
-  public static final String WEB_ENFORCED_JVM_ARGS = "-Djava.awt.headless=true -Dfile.encoding=UTF-8";
-
-  public static final String CE_ENFORCED_JVM_ARGS = "-Djava.awt.headless=true -Dfile.encoding=UTF-8";
-
   public static final String HTTP_PROXY_HOST = "http.proxyHost";
   public static final String HTTPS_PROXY_HOST = "https.proxyHost";
   public static final String HTTP_PROXY_PORT = "http.proxyPort";
@@ -121,25 +117,7 @@ public class ProcessProperties {
     Properties defaults = new Properties();
     defaults.put(SEARCH_HOST, InetAddress.getLoopbackAddress().getHostAddress());
     defaults.put(SEARCH_PORT, "9001");
-    defaults.put(SEARCH_JAVA_OPTS, "-Xms512m" +
-      " -Xmx512m"  +
-      " -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"  +
-      " -XX:+HeapDumpOnOutOfMemoryError");
+    defaults.put(SEARCH_JAVA_OPTS, "-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError");
     defaults.put(SEARCH_JAVA_ADDITIONAL_OPTS, "");
 
     defaults.put(PATH_DATA, "data");
index 83cfc0fb4970cb2576705659b4e8727e59879f35..3a880ea6a50267aacb850646c71baf840650ded2 100644 (file)
@@ -27,16 +27,19 @@ import java.util.Properties;
 import javax.annotation.Nullable;
 import org.sonar.process.ProcessId;
 
+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 = new HashMap<>(System.getenv());
-  private File workDir;
+  private final File workDir;
 
-  protected AbstractCommand(ProcessId id) {
-    this.id = id;
+  protected AbstractCommand(ProcessId id, File workDir) {
+    this.id = requireNonNull(id, "ProcessId can't be null");
+    this.workDir = requireNonNull(workDir, "workDir can't be null");
   }
 
   public ProcessId getProcessId() {
@@ -47,11 +50,6 @@ public abstract class AbstractCommand<T extends AbstractCommand> {
     return workDir;
   }
 
-  public T setWorkDir(File workDir) {
-    this.workDir = workDir;
-    return castThis();
-  }
-
   @SuppressWarnings("unchecked")
   private T castThis() {
     return (T) this;
index 24995b85f0311d92e340d169c987a1ceecf0efff..987ea7cb23245c49e791aa8c399b017f0ec00923 100644 (file)
@@ -27,6 +27,10 @@ import org.sonar.process.ProcessProperties;
 import org.sonar.process.Props;
 import org.sonar.process.es.EsLogging;
 import org.sonar.process.es.EsSettings;
+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;
@@ -48,9 +52,11 @@ public class CommandFactoryImpl implements CommandFactory {
     "socksProxyPort"};
 
   private final Props props;
+  private final File tempDir;
 
-  public CommandFactoryImpl(Props props) {
+  public CommandFactoryImpl(Props props, File tempDir) {
     this.props = props;
+    this.tempDir = tempDir;
   }
 
   @Override
@@ -65,8 +71,7 @@ public class CommandFactoryImpl implements CommandFactory {
 
     File logDir = new File(settingsMap.get("path.logs"));
     File confDir = new File(settingsMap.get("path.conf"));
-    EsCommand res = new EsCommand(ProcessId.ELASTICSEARCH)
-      .setWorkDir(executable.getParentFile().getParentFile())
+    EsCommand res = new EsCommand(ProcessId.ELASTICSEARCH, executable.getParentFile().getParentFile())
       .setExecutable(executable)
       .setConfDir(confDir)
       .setLog4j2Properties(new EsLogging().createProperties(props, logDir))
@@ -74,8 +79,9 @@ public class CommandFactoryImpl implements CommandFactory {
       .setClusterName(settingsMap.get("cluster.name"))
       .setHost(settingsMap.get("network.host"))
       .setPort(Integer.valueOf(settingsMap.get("transport.tcp.port")))
-      .addJvmOption(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
-      .addJvmOption(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
+      .setEsJvmOptions(new EsJvmOptions()
+        .addFromMandatoryProperty(props, ProcessProperties.SEARCH_JAVA_OPTS)
+        .addFromMandatoryProperty(props, ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
       .setEnvVariable("JAVA_HOME", System.getProperties().getProperty("java.home"));
 
     settingsMap.forEach((key, value) -> res.addEsOption("-E" + key + "=" + value));
@@ -93,10 +99,15 @@ public class CommandFactoryImpl implements CommandFactory {
   @Override
   public JavaCommand createWebCommand(boolean leader) {
     File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
-    JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, homeDir)
-      .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS)
-      .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS))
-      .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS))
+
+    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))
@@ -113,10 +124,15 @@ public class CommandFactoryImpl implements CommandFactory {
   @Override
   public JavaCommand createCeCommand() {
     File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
-    JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, homeDir)
-      .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS)
-      .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_OPTS))
-      .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS))
+
+    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/*")
@@ -128,27 +144,22 @@ public class CommandFactoryImpl implements CommandFactory {
     return command;
   }
 
-  private JavaCommand newJavaCommand(ProcessId id, File homeDir) {
-    JavaCommand command = new JavaCommand(id)
-      .setWorkDir(homeDir)
-      .setArguments(props.rawProperties());
-
+  private <T extends JvmOptions> void addProxyJvmOptions(JvmOptions<T> jvmOptions) {
     for (String key : PROXY_PROPERTY_KEYS) {
-      getPropsValue(key).ifPresent(val -> command.addJavaOption("-D" + key + "=" + val));
+      getPropsValue(key).ifPresent(val -> jvmOptions.add("-D" + key + "=" + val));
     }
 
     // defaults of HTTPS are the same than HTTP defaults
-    setSystemPropertyToDefaultIfNotSet(command, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
-    setSystemPropertyToDefaultIfNotSet(command, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
-    return command;
+    setSystemPropertyToDefaultIfNotSet(jvmOptions, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
+    setSystemPropertyToDefaultIfNotSet(jvmOptions, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
   }
 
-  private void setSystemPropertyToDefaultIfNotSet(JavaCommand command,
+  private void setSystemPropertyToDefaultIfNotSet(JvmOptions jvmOptions,
     String httpsProperty, String httpProperty) {
     Optional<String> httpValue = getPropsValue(httpProperty);
     Optional<String> httpsValue = getPropsValue(httpsProperty);
     if (!httpsValue.isPresent() && httpValue.isPresent()) {
-      command.addJavaOption("-D" + httpsProperty + "=" + httpValue.get());
+      jvmOptions.add("-D" + httpsProperty + "=" + httpValue.get());
     }
   }
 
index cd6877d493dbe73be34eaf2dd7f79630d147b075..6f1d374d77011d03c658101548ec7fff0d151f8d 100644 (file)
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
 import org.sonar.process.ProcessId;
+import org.sonar.process.jmvoptions.EsJvmOptions;
 
 public class EsCommand extends AbstractCommand<EsCommand> {
   private File executable;
@@ -33,10 +34,10 @@ public class EsCommand extends AbstractCommand<EsCommand> {
   private int port;
   private Properties log4j2Properties;
   private List<String> esOptions = new ArrayList<>();
-  private List<String> jvmOptions = new ArrayList<>();
+  private EsJvmOptions esJvmOptions;
 
-  public EsCommand(ProcessId id) {
-    super(id);
+  public EsCommand(ProcessId id, File workDir) {
+    super(id, workDir);
   }
 
   public File getExecutable() {
@@ -104,15 +105,12 @@ public class EsCommand extends AbstractCommand<EsCommand> {
     return this;
   }
 
-  public List<String> getJvmOptions() {
-    return jvmOptions;
-  }
-
-  public EsCommand addJvmOption(String s) {
-    if (!s.isEmpty()) {
-      jvmOptions.add(s);
-    }
+  public EsCommand setEsJvmOptions(EsJvmOptions esJvmOptions) {
+    this.esJvmOptions = esJvmOptions;
     return this;
   }
 
+  public EsJvmOptions getEsJvmOptions() {
+    return esJvmOptions;
+  }
 }
index 253c6294fbbc0c2aa12d3273ddf5a9cb7ea409dc..7659539b41b561bb82fd2c8e9b6cb09b7d9f22b2 100644 (file)
  */
 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.jmvoptions.JvmOptions;
 
-public class JavaCommand extends AbstractCommand<JavaCommand> {
+public class JavaCommand<T extends JvmOptions> extends AbstractCommand<JavaCommand<T>> {
   // entry point
   private String className;
-  // for example -Xmx1G
-  private final List<String> javaOptions = new ArrayList<>();
+  private JvmOptions<T> jvmOptions;
   // relative path to JAR files
   private final List<String> classpath = new ArrayList<>();
 
-  public JavaCommand(ProcessId id) {
-    super(id);
+  public JavaCommand(ProcessId id, File workDir) {
+    super(id, workDir);
   }
 
-  public List<String> getJavaOptions() {
-    return javaOptions;
+  public JvmOptions<T> getJvmOptions() {
+    return jvmOptions;
   }
 
-  public JavaCommand addJavaOption(String s) {
-    if (!s.isEmpty()) {
-      javaOptions.add(s);
-    }
-    return this;
-  }
+  public JavaCommand<T> setJvmOptions(JvmOptions<T> jvmOptions) {
+    this.jvmOptions = jvmOptions;
 
-  public JavaCommand addJavaOptions(String s) {
-    for (String opt : s.split(" ")) {
-      addJavaOption(opt);
-    }
     return this;
   }
 
@@ -57,7 +50,7 @@ public class JavaCommand extends AbstractCommand<JavaCommand> {
     return className;
   }
 
-  public JavaCommand setClassName(String className) {
+  public JavaCommand<T> setClassName(String className) {
     this.className = className;
     return this;
   }
@@ -66,21 +59,19 @@ public class JavaCommand extends AbstractCommand<JavaCommand> {
     return classpath;
   }
 
-  public JavaCommand addClasspath(String s) {
+  public JavaCommand<T> addClasspath(String s) {
     classpath.add(s);
     return this;
   }
 
   @Override
   public String toString() {
-    StringBuilder sb = new StringBuilder("JavaCommand{");
-    sb.append("workDir=").append(getWorkDir());
-    sb.append(", javaOptions=").append(javaOptions);
-    sb.append(", className='").append(className).append('\'');
-    sb.append(", classpath=").append(classpath);
-    sb.append(", arguments=").append(getArguments());
-    sb.append(", envVariables=").append(getEnvVariables());
-    sb.append('}');
-    return sb.toString();
+    return "JavaCommand{" + "workDir=" + getWorkDir() +
+      ", jvmOptions=" + jvmOptions +
+      ", className='" + className + '\'' +
+      ", classpath=" + classpath +
+      ", arguments=" + getArguments() +
+      ", envVariables=" + getEnvVariables() +
+      '}';
   }
 }
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
new file mode 100644 (file)
index 0000000..4b0fc96
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 static java.lang.String.format;
+
+public class CeJvmOptions extends JvmOptions<CeJvmOptions> {
+  private static final String[] MANDATORY_JVM_OPTIONS = {"-Djava.awt.headless=true", "-Dfile.encoding=UTF-8"};
+
+  public CeJvmOptions(File tmpDir) {
+    super(MANDATORY_JVM_OPTIONS);
+    add(format("-Djava.io.tmpdir=%s", tmpDir.getAbsolutePath()));
+  }
+}
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
new file mode 100644 (file)
index 0000000..1c823e9
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.stream.Collectors;
+
+public class EsJvmOptions extends JvmOptions<EsJvmOptions> {
+  private static final String[] MANDATORY_OPTIONS = {
+    "-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"
+  };
+  private static final String ELASTICSEARCH_JVM_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
+    "# Please use the sonar.search.javaOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
+    "\n" +
+    "# DO NOT EDIT THIS FILE\n" +
+    "\n";
+
+  public EsJvmOptions() {
+    super(MANDATORY_OPTIONS);
+  }
+
+  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
new file mode 100644 (file)
index 0000000..15cc411
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.LinkedHashSet;
+import java.util.List;
+import org.sonar.process.Props;
+
+import static java.util.Objects.requireNonNull;
+
+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 LinkedHashSet<String> options = new LinkedHashSet<>();
+
+  public JvmOptions(String... mandatoryJvmOptions) {
+    Arrays.stream(requireNonNull(mandatoryJvmOptions, JVM_OPTION_NOT_NULL_ERROR_MESSAGE))
+      .forEach(this::add);
+  }
+
+  public T addFromMandatoryProperty(Props props, String propertyName) {
+    String value = props.nonNullValue(propertyName);
+    if (!value.isEmpty()) {
+      Arrays.stream(value.split(" (?=-)")).forEach(this::add);
+    }
+
+    return castThis();
+  }
+
+  /**
+   * 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 (value.isEmpty() || !value.startsWith("-")) {
+      throw new IllegalArgumentException("a JVM option can't be empty and must start with '-'");
+    }
+    options.add(value);
+
+    return castThis();
+  }
+
+  @SuppressWarnings("unchecked")
+  private T castThis() {
+    return (T) this;
+  }
+
+  public List<String> getAll() {
+    return new ArrayList<>(options);
+  }
+
+  @Override
+  public String toString() {
+    return options.toString();
+  }
+}
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
new file mode 100644 (file)
index 0000000..dda9856
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 static java.lang.String.format;
+
+public class WebJvmOptions extends JvmOptions<WebJvmOptions> {
+  private static final String[] MANDATORY_OPTIONS = {"-Djava.awt.headless=true", "-Dfile.encoding=UTF-8"};
+
+  public WebJvmOptions(File tmpDir) {
+    super(MANDATORY_OPTIONS);
+    add(format("-Djava.io.tmpdir=%s", tmpDir.getAbsolutePath()));
+  }
+}
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
new file mode 100644 (file)
index 0000000..f88e33e
--- /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.process.jmvoptions;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 7ddb9c5408940c2733bc3e472674c9c15233217d..62f59f11a48843df7d1ead7fcf0f54656d860334 100644 (file)
 package org.sonar.process.command;
 
 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.ProcessId;
 
@@ -32,10 +34,33 @@ 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()) {
+
+    };
+  }
+
+  @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) {
+
+    };
+  }
 
   @Test
   public void test_command_with_complete_information() throws Exception {
-    AbstractCommand command = new AbstractCommand(ProcessId.ELASTICSEARCH) {
+    File workDir = temp.newFolder();
+    AbstractCommand command = new AbstractCommand(ProcessId.ELASTICSEARCH, workDir) {
 
     };
 
@@ -45,8 +70,6 @@ public class AbstractCommandTest {
     command.setArguments(args);
 
     command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
-    File workDir = temp.newFolder();
-    command.setWorkDir(workDir);
 
     assertThat(command.toString()).isNotNull();
     assertThat(command.getWorkDir()).isSameAs(workDir);
index 96d266a7d28781da45a5720fd4d12f721edcafd3..dd0f71a677c73e45a59ca8b6de9f3ca036948cc2 100644 (file)
@@ -25,6 +25,7 @@ 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;
 
@@ -35,7 +36,8 @@ public class JavaCommandTest {
 
   @Test
   public void test_command_with_complete_information() throws Exception {
-    JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
+    File workDir = temp.newFolder();
+    JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, workDir);
 
     command.setArgument("first_arg", "val1");
     Properties args = new Properties();
@@ -44,15 +46,14 @@ public class JavaCommandTest {
 
     command.setClassName("org.sonar.ElasticSearch");
     command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
-    File workDir = temp.newFolder();
-    command.setWorkDir(workDir);
     command.addClasspath("lib/*.jar");
     command.addClasspath("conf/*.xml");
-    command.addJavaOption("-Xmx128m");
+    JvmOptions<JvmOptions> jvmOptions = new JvmOptions<JvmOptions>() {};
+    command.setJvmOptions(jvmOptions);
 
     assertThat(command.toString()).isNotNull();
     assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml");
-    assertThat(command.getJavaOptions()).containsOnly("-Xmx128m");
+    assertThat(command.getJvmOptions()).isSameAs(jvmOptions);
     assertThat(command.getWorkDir()).isSameAs(workDir);
     assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch");
 
@@ -61,15 +62,4 @@ public class JavaCommandTest {
     assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1);
   }
 
-  @Test
-  public void addJavaOptions_adds_jvm_options() {
-    JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
-    assertThat(command.getJavaOptions()).isEmpty();
-
-    command.addJavaOptions("");
-    assertThat(command.getJavaOptions()).isEmpty();
-
-    command.addJavaOptions("-Xmx512m -Xms256m -Dfoo");
-    assertThat(command.getJavaOptions()).containsOnly("-Xmx512m", "-Xms256m", "-Dfoo");
-  }
 }
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
new file mode 100644 (file)
index 0000000..3bd8bf3
--- /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.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
new file mode 100644 (file)
index 0000000..5f4ef87
--- /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.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 the sonar.search.javaOpts 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
new file mode 100644 (file)
index 0000000..7e42d30
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * 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.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.ArrayList;
+import java.util.Properties;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class JvmOptionsTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private final Random random = new Random();
+  private final String randomPropertyName = RandomStringUtils.randomAlphanumeric(3);
+  private final Properties properties = new Properties();
+  private final Props props = new Props(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_argument_is_null() {
+    ArrayList<String> nullList = new ArrayList<>();
+    nullList.add(null);
+    String[] arguments = Stream.of(
+      Stream.of("-S1"),
+      IntStream.range(0, random.nextInt(2)).mapToObj(i -> "-B" + i),
+      nullList.stream(),
+      IntStream.range(0, random.nextInt(2)).mapToObj(i -> "-A" + i)).flatMap(s -> s)
+      .toArray(String[]::new);
+
+    expectJvmOptionNotNullNPE();
+
+    new JvmOptions(arguments);
+  }
+
+  @Test
+  @UseDataProvider("variousEmptyStrings")
+  public void constructor_throws_IAE_if_argument_is_empty(String emptyString) {
+    expectJvmOptionNotEmptyAndStartByDashIAE();
+
+    new JvmOptions(emptyString);
+  }
+
+  @Test
+  @UseDataProvider("variousEmptyStrings")
+  public void constructor_throws_IAE_if_any_argument_is_empty(String emptyString) {
+    String[] arguments = Stream.of(
+      Stream.of("-S1"),
+      IntStream.range(0, random.nextInt(2)).mapToObj(i -> "-B" + i),
+      Stream.of(emptyString),
+      IntStream.range(0, random.nextInt(2)).mapToObj(i -> "-A" + i))
+      .flatMap(s -> s)
+      .toArray(String[]::new);
+
+    expectJvmOptionNotEmptyAndStartByDashIAE();
+
+    new JvmOptions(arguments);
+  }
+
+  @Test
+  public void constructor_throws_IAE_if_argument_does_not_start_with_dash() {
+    expectJvmOptionNotEmptyAndStartByDashIAE();
+
+    new JvmOptions(RandomStringUtils.randomAlphanumeric(3));
+  }
+
+  @Test
+  public void constructor_throws_IAE_if_any_argument_does_not_start_with_dash() {
+    String[] arguments = Stream.of(
+      Stream.of("-S1"),
+      IntStream.range(0, random.nextInt(2)).mapToObj(i -> "-B" + i),
+      Stream.of(RandomStringUtils.randomAlphanumeric(3)),
+      IntStream.range(0, random.nextInt(2)).mapToObj(i -> "-A" + i))
+      .flatMap(s -> s)
+      .toArray(String[]::new);
+
+    expectJvmOptionNotEmptyAndStartByDashIAE();
+
+    new JvmOptions(arguments);
+  }
+
+  @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(RandomStringUtils.randomAlphanumeric(3));
+  }
+
+  @Test
+  public void addFromMandatoryProperty_fails_with_IAE_if_property_does_not_exist() {
+    expectMissingPropertyIAE(this.randomPropertyName);
+
+    underTest.addFromMandatoryProperty(props, this.randomPropertyName);
+  }
+
+  @Test
+  @UseDataProvider("variousEmptyStrings")
+  public void addFromMandatoryProperty_fails_with_IAE_if_property_contains_an_empty_value(String emptyString) {
+    expectMissingPropertyIAE(this.randomPropertyName);
+
+    underTest.addFromMandatoryProperty(props, randomPropertyName);
+  }
+
+  @Test
+  @UseDataProvider("variousEmptyStrings")
+  public void addFromMandatoryProperty_adds_single_option_of_property_with_trimming(String emptyString) {
+    properties.put(randomPropertyName, emptyString + "-foo" + emptyString);
+
+    underTest.addFromMandatoryProperty(props, randomPropertyName);
+
+    assertThat(underTest.getAll()).containsOnly("-foo");
+  }
+
+  @Test
+  @UseDataProvider("variousEmptyStrings")
+  public void addFromMandatoryProperty_fails_with_IAE_if_property_does_not_start_with_dash_after_trimmed(String emptyString) {
+    properties.put(randomPropertyName, emptyString + "foo -bar");
+
+    expectJvmOptionNotEmptyAndStartByDashIAE();
+
+    underTest.addFromMandatoryProperty(props, 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(props, randomPropertyName);
+
+    assertThat(underTest.getAll()).containsOnly("-foo", "-bar", "-duck");
+  }
+
+  @Test
+  public void addFromMandatoryProperty_supports_spaces_inside_options() {
+    properties.put(randomPropertyName, "-foo bar -duck");
+
+    underTest.addFromMandatoryProperty(props, randomPropertyName);
+
+    assertThat(underTest.getAll()).containsOnly("-foo bar", "-duck");
+  }
+
+  @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 '-'");
+  }
+
+  public void expectMissingPropertyIAE(String randomPropertyName) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Missing property: " + randomPropertyName);
+  }
+
+  @DataProvider()
+  public static Object[][] variousEmptyStrings() {
+    return new Object[][] {
+      {""},
+      {" "},
+      {"     "}
+    };
+  }
+}
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
new file mode 100644 (file)
index 0000000..9387a15
--- /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.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());
+  }
+
+}
index a2e16d2cc54556a43f14bb8ce4f3e441a6ca8e54..94ba3a3a5ff7b4223db69169932e49537ab77a5a 100644 (file)
 #--------------------------------------------------------------------------------------------------
 # ELASTICSEARCH
 # Elasticsearch is used to facilitate fast and accurate information retrieval.
-# It is executed in a dedicated Java process. Default heap size is 512m.
+# It is executed in a dedicated Java process. Default heap size is 512Mb.
 
 # JVM options of Elasticsearch process
 #sonar.search.javaOpts=-Xms512m \
 # -Xmx512m \
-# -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 \
 # -XX:+HeapDumpOnOutOfMemoryError
 
 # Same as previous property, but allows to not repeat all other settings like -Xmx
index b2010900e58cec1c1bc8fb46ccc3e572c2e9316e..123467455eece19627d3ee93fe52ff08ea544d66 100644 (file)
@@ -49,8 +49,8 @@ public class App {
     try (AppState appState = new AppStateFactory(settings).create()) {
       appState.registerSonarQubeVersion(getSonarqubeVersion());
       AppReloader appReloader = new AppReloaderImpl(settingsLoader, fileSystem, appState, logging);
-      CommandFactory commandFactory = new CommandFactoryImpl(settings.getProps());
       fileSystem.reset();
+      CommandFactory commandFactory = new CommandFactoryImpl(settings.getProps(), fileSystem.getTempDir());
 
       try (ProcessLauncher processLauncher = new ProcessLauncherImpl(fileSystem.getTempDir())) {
         Scheduler scheduler = new SchedulerImpl(settings, appReloader, commandFactory, processLauncher, appState);
index 1c687510121ab0a97ba900d47b73c050901e2ce4..c016590617c7075e00757223e0ab2f2bebadcca9 100644 (file)
@@ -64,7 +64,7 @@ public class ExceptionCauseMatcher extends TypeSafeMatcher<Throwable> {
 
   @Override
   protected boolean matchesSafely(Throwable item) {
-    if (!item.getClass().isAssignableFrom(type)) {
+    if (!type.isAssignableFrom(item.getClass())) {
       return false;
     }
     if (expectedMessage == null) {
index 524e957bbb2f27ecad86b2ab62bc8a3b7da21f12..d99720785b22eb784ffda42c1250dd863ccc3585 100644 (file)
@@ -127,12 +127,6 @@ public class Category1Suite {
 
     .addPlugin(pluginArtifact("posttask-plugin"))
 
-    // reduce xmx and xms from 2g (default) to 1g
-    .setServerProperty("sonar.search.javaOpts", "-Xms512m -Xmx512m -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 -XX:+HeapDumpOnOutOfMemoryError")
-
     .addPlugin(xooPlugin())
     .build();
 
index df2397d864209a07ca17e6880d16168420d3622c..c101253c84a4cfb23cedaaa74c5502ec6b3b25ff 100644 (file)
@@ -109,12 +109,6 @@ public class Category2Suite {
     // 1 second. Required for notification test.
     .setServerProperty("sonar.notifications.delay", "1")
 
-    // reduce xmx and xms from 2g (default) to 1g
-    .setServerProperty("sonar.search.javaOpts", "-Xms512m -Xmx512m -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 -XX:+HeapDumpOnOutOfMemoryError")
-
     .build();
 
 }
index 7407fcff030667d10070f2f614b58338970f02e9..25dd138a78ebf906db0eb2a60f36e6e489fc6fb9 100644 (file)
@@ -86,11 +86,5 @@ public class Category3Suite {
     // used by ProjectBuilderTest
     .addPlugin(pluginArtifact("project-builder-plugin"))
 
-    // reduce xmx and xms from 2g (default) to 1g
-    .setServerProperty("sonar.search.javaOpts", "-Xms512m -Xmx512m -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 -XX:+HeapDumpOnOutOfMemoryError")
-
     .build();
 }
index f7ab6e9a49948fcc90d60c73a23c13217cf04461..6e50225b59a2812219793bb877cb4a08f83758ff 100644 (file)
@@ -125,11 +125,5 @@ public class Category4Suite {
     // Used by LogsTest
     .setServerProperty("sonar.web.accessLogs.pattern", LogsTest.ACCESS_LOGS_PATTERN)
 
-    // reduce xmx and xms from 2g (default) to 1g
-    .setServerProperty("sonar.search.javaOpts", "-Xms512m -Xmx512m -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 -XX:+HeapDumpOnOutOfMemoryError")
-
     .build();
 }
index a144c00b3bd980ac57126bfd2a7cd6733df5cbb7..9d0c046703ce6b0da375a3af4b405693bcd0ee56 100644 (file)
@@ -98,11 +98,5 @@ public class Category6Suite {
     .addPlugin(pluginArtifact("fake-billing-plugin"))
     .addPlugin(pluginArtifact("ui-extensions-plugin"))
 
-    // reduce xmx and xms from 2g (default) to 1g
-    .setServerProperty("sonar.search.javaOpts", "-Xms512m -Xmx512m -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 -XX:+HeapDumpOnOutOfMemoryError")
-
     .build();
 }