]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9590 rename sonar-process-monitor to sonar-main
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 18 Aug 2017 15:13:49 +0000 (17:13 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 5 Sep 2017 12:24:12 +0000 (14:24 +0200)
148 files changed:
server/pom.xml
server/sonar-main/pom.xml [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppFileSystem.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppLogging.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppReloader.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppReloaderImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppState.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppStateImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/AppStateListener.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/FileSystem.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/NodeLifecycle.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/Scheduler.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProcess.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/cluster/HazelcastCluster.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/cluster/package-info.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/AppSettings.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsLoader.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/CommandLineParser.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/FileSystemSettings.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/JdbcSettings.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/config/package-info.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/package-info.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/AbstractProcessMonitor.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/Lifecycle.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/ProcessEventListener.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncher.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/ProcessMonitor.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/SQProcess.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/StopRequestWatcher.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/StreamGobbler.java [new file with mode: 0644]
server/sonar-main/src/main/java/org/sonar/application/process/package-info.java [new file with mode: 0644]
server/sonar-main/src/main/resources/org/sonar/application/process/elasticsearch.yml [new file with mode: 0644]
server/sonar-main/src/main/resources/sonarqube-version.txt [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/AppFileSystemTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/AppReloaderImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/AppStateImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/TestAppState.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/AppSettingsImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsLoopbackTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/CommandLineParserTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/JdbcSettingsTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/config/TestAppSettings.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/LifecycleTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/SQProcessTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java [new file with mode: 0644]
server/sonar-main/src/test/java/org/sonar/application/process/StreamGobblerTest.java [new file with mode: 0644]
server/sonar-main/src/test/resources/logback-test.xml [new file with mode: 0644]
server/sonar-process-monitor/pom.xml [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloader.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloaderImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateFactory.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateListener.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/FileSystem.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/NodeLifecycle.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/Scheduler.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/SchedulerImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProcess.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProperties.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/HazelcastCluster.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/package-info.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettings.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoader.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/ClusterSettings.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/CommandLineParser.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/FileSystemSettings.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/JdbcSettings.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/config/package-info.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/package-info.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/AbstractProcessMonitor.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsProcessMonitor.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/Lifecycle.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessEventListener.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncher.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitor.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/SQProcess.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcher.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/StreamGobbler.java [deleted file]
server/sonar-process-monitor/src/main/java/org/sonar/application/process/package-info.java [deleted file]
server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml [deleted file]
server/sonar-process-monitor/src/main/resources/sonarqube-version.txt [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/AppLoggingTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/AppReloaderImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsLoopbackTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/CommandLineParserTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/JdbcSettingsTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/config/TestAppSettings.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/process/LifecycleTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/process/SQProcessTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java [deleted file]
server/sonar-process-monitor/src/test/java/org/sonar/application/process/StreamGobblerTest.java [deleted file]
server/sonar-process-monitor/src/test/resources/logback-test.xml [deleted file]
sonar-application/pom.xml

index 6ecf00574068ef61895a3f96810d726a543f7384..bd0564d4620349fece933130f510aa0a71c89778 100644 (file)
@@ -14,7 +14,7 @@
 
   <modules>
     <module>sonar-process</module>
-    <module>sonar-process-monitor</module>
+    <module>sonar-main</module>
     <module>sonar-db-core</module>
     <module>sonar-db-migration</module>
     <module>sonar-db-dao</module>
diff --git a/server/sonar-main/pom.xml b/server/sonar-main/pom.xml
new file mode 100644 (file)
index 0000000..9aa7985
--- /dev/null
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.sonarsource.sonarqube</groupId>
+    <artifactId>server</artifactId>
+    <version>6.6-SNAPSHOT</version>
+    <relativePath>../</relativePath>
+  </parent>
+
+  <artifactId>sonar-main</artifactId>
+  <name>SonarQube :: Main Process</name>
+
+  <properties>
+    <!--
+     version as stored in JAR and displayed in webapp. It is
+     overridden on Travis when replacing SNAPSHOT version by
+     build unique version, for instance "6.3.0.12345".
+     -->
+    <buildVersion>${project.version}</buildVersion>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>sonar-process</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-to-slf4j</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.hazelcast</groupId>
+      <artifactId>hazelcast</artifactId>
+    </dependency>
+    <!--
+    Required by our usage of Guava for clustering : CeWorkerFactoryImpl.getClusteredWorkerUUIDs()
+    -->
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.elasticsearch.client</groupId>
+      <artifactId>transport</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.hazelcast</groupId>
+      <artifactId>hazelcast-client</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <!-- Used to resolve variables in file sonarqube-version.txt -->
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppFileSystem.java b/server/sonar-main/src/main/java/org/sonar/application/AppFileSystem.java
new file mode 100644 (file)
index 0000000..f397d60
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.EnumSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.config.AppSettings;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
+
+import static java.lang.String.format;
+import static java.nio.file.FileVisitResult.CONTINUE;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+import static org.sonar.process.FileUtils2.deleteDirectory;
+import static org.sonar.process.ProcessProperties.PATH_DATA;
+import static org.sonar.process.ProcessProperties.PATH_LOGS;
+import static org.sonar.process.ProcessProperties.PATH_TEMP;
+import static org.sonar.process.ProcessProperties.PATH_WEB;
+
+public class AppFileSystem implements FileSystem {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AppFileSystem.class);
+  private static final EnumSet<FileVisitOption> FOLLOW_LINKS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
+
+  private final AppSettings settings;
+
+  public AppFileSystem(AppSettings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public void reset() throws IOException {
+    createDirectory(PATH_DATA);
+    createDirectory(PATH_WEB);
+    createDirectory(PATH_LOGS);
+    File tempDir = createOrCleanTempDirectory(PATH_TEMP);
+    try (AllProcessesCommands allProcessesCommands = new AllProcessesCommands(tempDir)) {
+      allProcessesCommands.clean();
+    }
+  }
+
+  @Override
+  public File getTempDir() {
+    return settings.getProps().nonNullValueAsFile(PATH_TEMP);
+  }
+
+  private boolean createDirectory(String propKey) throws IOException {
+    File dir = settings.getProps().nonNullValueAsFile(propKey);
+    if (dir.exists()) {
+      ensureIsNotAFile(propKey, dir);
+      return false;
+    }
+
+    forceMkdir(dir);
+    ensureIsNotAFile(propKey, dir);
+    return true;
+  }
+
+  private static void ensureIsNotAFile(String propKey, File dir) {
+    if (!dir.isDirectory()) {
+      throw new IllegalStateException(format("Property '%s' is not valid, not a directory: %s",
+        propKey, dir.getAbsolutePath()));
+    }
+  }
+
+  private File createOrCleanTempDirectory(String propKey) throws IOException {
+    File dir = settings.getProps().nonNullValueAsFile(propKey);
+    LOG.info("Cleaning or creating temp directory {}", dir.getAbsolutePath());
+    if (!createDirectory(propKey)) {
+      Files.walkFileTree(dir.toPath(), FOLLOW_LINKS, CleanTempDirFileVisitor.VISIT_MAX_DEPTH, new CleanTempDirFileVisitor(dir.toPath()));
+    }
+    return dir;
+  }
+
+  private static class CleanTempDirFileVisitor extends SimpleFileVisitor<Path> {
+    private static final Path SHAREDMEMORY_FILE = Paths.get("sharedmemory");
+    static final int VISIT_MAX_DEPTH = 1;
+
+    private final Path path;
+    private final boolean symLink;
+
+    CleanTempDirFileVisitor(Path path) {
+      this.path = path;
+      this.symLink = Files.isSymbolicLink(path);
+    }
+
+    @Override
+    public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
+      File file = filePath.toFile();
+      if (file.isDirectory()) {
+        deleteDirectory(file);
+      } else if (filePath.getFileName().equals(SHAREDMEMORY_FILE)) {
+        return CONTINUE;
+      } else if (!symLink || !filePath.equals(path)) {
+        Files.delete(filePath);
+      }
+      return CONTINUE;
+    }
+
+    @Override
+    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+      if (!dir.equals(path)) {
+        deleteDirectory(dir.toFile());
+      }
+      return CONTINUE;
+    }
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppLogging.java b/server/sonar-main/src/main/java/org/sonar/application/AppLogging.java
new file mode 100644 (file)
index 0000000..5e19aab
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.FileAppender;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.process.StreamGobbler;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.logging.LogLevelConfig;
+import org.sonar.process.logging.LogbackHelper;
+import org.sonar.process.logging.RootLoggerConfig;
+
+import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
+
+/**
+ * Configure logback for the APP process.
+ *
+ * <p>
+ * SonarQube's logging use cases:
+ * <ol>
+ *   <li>
+ *     SQ started as a background process (with {@code sonar.sh start}):
+ *     <ul>
+ *       <li>
+ *         logs produced by the JVM before logback is setup in the APP JVM or which can't be caught by logback
+ *         (such as JVM crash) must be written to sonar.log
+ *       </li>
+ *       <li>
+ *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
+ *         by logback (such as JVM crash) must be written to sonar.log
+ *       </li>
+ *       <li>each JVM writes its own logs into its dedicated file</li>
+ *     </ul>
+ *   </li>
+ *   <li>
+ *     SQ started in console with wrapper (ie. with {@code sonar.sh console}):
+ *     <ul>
+ *       <li>
+ *         logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback
+ *         (such as JVM crash) must be written to sonar.log
+ *       </li>
+ *       <li>
+ *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
+ *         by logback (such as JVM crash) must be written to sonar.log
+ *       </li>
+ *       <li>each JVM writes its own logs into its dedicated file</li>
+ *       <li>APP JVM logs are written to the APP JVM {@code System.out}</li>
+ *     </ul>
+ *   </li>
+ *   <li>
+ *     SQ started from command line (ie. {@code java -jar sonar-application-X.Y.jar}):
+ *     <ul>
+ *       <li>
+ *         logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback
+ *         (such as JVM crash) are the responsibility of the user to be dealt with
+ *       </li>
+ *       <li>
+ *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
+ *         by logback (such as JVM crash) must be written to APP's {@code System.out}
+ *       </li>
+ *       <li>each JVM writes its own logs into its dedicated file</li>
+ *       <li>APP JVM logs are written to the APP JVM {@code System.out}</li>
+ *     </ul>
+ *   </li>
+ *   <li>
+ *     SQ started from an IT (ie. from command line with {@code option -Dsonar.log.console=true}):
+ *     <ul>
+ *       <li>
+ *         logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback
+ *         (such as JVM crash) are the responsibility of the developer or maven to be dealt with
+ *       </li>
+ *       <li>
+ *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
+ *         by logback (such as JVM crash) must be written to APP's {@code System.out} and are the responsibility of the
+ *         developer or maven to be dealt with
+ *       </li>
+ *       <li>each JVM writes its own logs into its dedicated file</li>
+ *       <li>logs of all 4 JVMs are also written to the APP JVM {@code System.out}</li>
+ *     </ul>
+ *   </li>
+ * </ol>
+ * </p>
+ *
+ */
+public class AppLogging {
+
+  private static final String CONSOLE_LOGGER = "console";
+  private static final String CONSOLE_PLAIN_APPENDER = "CONSOLE";
+  private static final String APP_CONSOLE_APPENDER = "APP_CONSOLE";
+  private static final String GOBBLER_PLAIN_CONSOLE = "GOBBLER_CONSOLE";
+  private static final RootLoggerConfig APP_ROOT_LOGGER_CONFIG = newRootLoggerConfigBuilder()
+    .setProcessId(ProcessId.APP)
+    .build();
+
+  private final LogbackHelper helper = new LogbackHelper();
+  private final AppSettings appSettings;
+
+  public AppLogging(AppSettings appSettings) {
+    this.appSettings = appSettings;
+  }
+
+  public LoggerContext configure() {
+    LoggerContext ctx = helper.getRootContext();
+    ctx.reset();
+
+    helper.enableJulChangePropagation(ctx);
+
+    configureConsole(ctx);
+    if (helper.isAllLogsToConsoleEnabled(appSettings.getProps()) || !appSettings.getProps().valueAsBoolean("sonar.wrapped", false)) {
+      configureWithLogbackWritingToFile(ctx);
+    } else {
+      configureWithWrapperWritingToFile(ctx);
+    }
+    helper.apply(
+      LogLevelConfig.newBuilder(helper.getRootLoggerName())
+        .rootLevelFor(ProcessId.APP)
+        .immutableLevel("com.hazelcast",
+          Level.toLevel(
+            appSettings.getProps().nonNullValue(ProcessProperties.HAZELCAST_LOG_LEVEL)))
+        .build(),
+      appSettings.getProps());
+
+    return ctx;
+  }
+
+  /**
+   * Creates a non additive logger dedicated to printing message as is (ie. assuming they are already formatted).
+   *
+   * It creates a dedicated appender to the System.out which applies no formatting the logs it receives.
+   */
+  private void configureConsole(LoggerContext loggerContext) {
+    ConsoleAppender<ILoggingEvent> consoleAppender = helper.newConsoleAppender(loggerContext, CONSOLE_PLAIN_APPENDER, "%msg%n");
+
+    Logger consoleLogger = loggerContext.getLogger(CONSOLE_LOGGER);
+    consoleLogger.setAdditive(false);
+    consoleLogger.addAppender(consoleAppender);
+  }
+
+  /**
+   * The process has been started by orchestrator (ie. via {@code java -jar} and optionally passing the option {@code -Dsonar.log.console=true}).
+   * Therefor, APP's System.out (and System.err) are <strong>not</strong> copied to sonar.log by the wrapper and
+   * printing to sonar.log must be done at logback level.
+   */
+  private void configureWithLogbackWritingToFile(LoggerContext ctx) {
+    // configure all logs (ie. root logger) to be written to sonar.log and also to the console with formatting
+    // in practice, this will be only APP's own logs as logs from sub processes LOGGER_GOBBLER and LOGGER_GOBBLER
+    // is configured below to be detached from root
+    // so, this will make all APP's log to be both written to sonar.log and visible in the console
+    configureRootWithLogbackWritingToFile(ctx);
+
+    // if option -Dsonar.log.console=true has been set, sub processes will write their logs to their own files but also
+    // copy them to their System.out.
+    // otherwise, the only logs to be expected in LOGGER_GOBBLER are those before logback is setup in subprocesses or
+    // when their JVM crashes
+    // they must be printed to App's System.out as is (as they are already formatted)
+    // logger is configured to be non additive as we don't want these logs to be written to sonar.log and duplicated in
+    // the console (with an incorrect formatting)
+    configureGobbler(ctx);
+  }
+
+  /**
+   * SQ has been started by the wrapper (ie. with sonar.sh) therefor, APP's System.out (and System.err) are written to
+   * sonar.log by the wrapper.
+   */
+  private void configureWithWrapperWritingToFile(LoggerContext ctx) {
+    // configure all logs (ie. root logger) to be written to console with formatting
+    // in practice, this will be only APP's own logs as logs from sub processes are written to LOGGER_GOBBLER and
+    // LOGGER_GOBBLER is configured below to be detached from root
+    // logs are written to the console because we want them to be in sonar.log and the wrapper will write any log
+    // from APP's System.out and System.err to sonar.log
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    rootLogger.addAppender(createAppConsoleAppender(ctx, helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG)));
+
+    // in regular configuration, sub processes are not copying their logs to their System.out, so, the only logs to be
+    // expected in LOGGER_GOBBLER are those before logback is setup in subprocesses or when JVM crashes
+    // so, they must be printed to App's System.out as is (as they are already formatted) and the wrapper will write
+    // them to sonar.log
+    // logger is configured to be non additive as we don't want these logs written to sonar.log and duplicated in the
+    // console with an incorrect formatting
+    configureGobbler(ctx);
+  }
+
+  private void configureRootWithLogbackWritingToFile(LoggerContext ctx) {
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    String appLogPattern = helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG);
+    FileAppender<ILoggingEvent> fileAppender = helper.newFileAppender(ctx, appSettings.getProps(), APP_ROOT_LOGGER_CONFIG, appLogPattern);
+    rootLogger.addAppender(fileAppender);
+    rootLogger.addAppender(createAppConsoleAppender(ctx, appLogPattern));
+  }
+
+  /**
+   * Configure the logger to which logs from sub processes are written to
+   * (called {@link StreamGobbler#LOGGER_GOBBLER}) by {@link StreamGobbler},
+   * to be:
+   * <ol>
+   *   <li>non additive (ie. these logs will be output by the appender of {@link StreamGobbler#LOGGER_GOBBLER} and only this one)</li>
+   *   <li>write logs as is (ie. without any extra formatting)</li>
+   *   <li>write exclusively to App's System.out</li>
+   * </ol>
+   */
+  private void configureGobbler(LoggerContext ctx) {
+    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
+    gobblerLogger.setAdditive(false);
+    gobblerLogger.addAppender(helper.newConsoleAppender(ctx, GOBBLER_PLAIN_CONSOLE, "%msg%n"));
+  }
+
+  private ConsoleAppender<ILoggingEvent> createAppConsoleAppender(LoggerContext ctx, String appLogPattern) {
+    return helper.newConsoleAppender(ctx, APP_CONSOLE_APPENDER, appLogPattern);
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppReloader.java b/server/sonar-main/src/main/java/org/sonar/application/AppReloader.java
new file mode 100644 (file)
index 0000000..f475e10
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import org.sonar.application.config.AppSettings;
+
+/**
+ * Reload settings, reset logging and file system when a
+ * server restart has been requested.
+ */
+public interface AppReloader {
+
+  /**
+   * This method is called when server is down.
+   */
+  void reload(AppSettings settings) throws IOException;
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppReloaderImpl.java b/server/sonar-main/src/main/java/org/sonar/application/AppReloaderImpl.java
new file mode 100644 (file)
index 0000000..3463c1e
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.Objects;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.AppSettingsLoader;
+import org.sonar.application.config.ClusterSettings;
+import org.sonar.process.MessageException;
+import org.sonar.process.Props;
+
+import static java.lang.String.format;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.PATH_DATA;
+import static org.sonar.process.ProcessProperties.PATH_LOGS;
+import static org.sonar.process.ProcessProperties.PATH_TEMP;
+import static org.sonar.process.ProcessProperties.PATH_WEB;
+
+public class AppReloaderImpl implements AppReloader {
+
+  private final AppSettingsLoader settingsLoader;
+  private final FileSystem fileSystem;
+  private final AppState appState;
+  private final AppLogging logging;
+
+  public AppReloaderImpl(AppSettingsLoader settingsLoader, FileSystem fileSystem, AppState appState, AppLogging logging) {
+    this.settingsLoader = settingsLoader;
+    this.fileSystem = fileSystem;
+    this.appState = appState;
+    this.logging = logging;
+  }
+
+  @Override
+  public void reload(AppSettings settings) throws IOException {
+    if (ClusterSettings.isClusterEnabled(settings)) {
+      throw new IllegalStateException("Restart is not possible with cluster mode");
+    }
+    AppSettings reloaded = settingsLoader.load();
+    ensureUnchangedConfiguration(settings.getProps(), reloaded.getProps());
+    settings.reload(reloaded.getProps());
+
+    fileSystem.reset();
+    logging.configure();
+    appState.reset();
+  }
+
+  private static void ensureUnchangedConfiguration(Props oldProps, Props newProps) {
+    verifyUnchanged(oldProps, newProps, PATH_DATA);
+    verifyUnchanged(oldProps, newProps, PATH_WEB);
+    verifyUnchanged(oldProps, newProps, PATH_LOGS);
+    verifyUnchanged(oldProps, newProps, PATH_TEMP);
+    verifyUnchanged(oldProps, newProps, CLUSTER_ENABLED);
+  }
+
+  private static void verifyUnchanged(Props initialProps, Props newProps, String propKey) {
+    String initialValue = initialProps.nonNullValue(propKey);
+    String newValue = newProps.nonNullValue(propKey);
+    if (!Objects.equals(initialValue, newValue)) {
+      throw new MessageException(format("Property [%s] cannot be changed on restart: [%s] => [%s]", propKey, initialValue, newValue));
+    }
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppState.java b/server/sonar-main/src/main/java/org/sonar/application/AppState.java
new file mode 100644 (file)
index 0000000..43cb69e
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import java.util.Optional;
+import org.sonar.process.ProcessId;
+
+public interface AppState extends AutoCloseable {
+
+  void addListener(AppStateListener listener);
+
+  /**
+   * Whether the process with the specified {@code processId}
+   * has been marked as operational.
+   *
+   * If parameter {@code local} is {@code true}, then only the
+   * process on the local node is requested.
+   *
+   * If parameter {@code local} is {@code false}, then only
+   * the processes on remote nodes are requested, excluding
+   * the local node. In this case at least one process must
+   * be marked as operational.
+   */
+  boolean isOperational(ProcessId processId, boolean local);
+
+  /**
+   * Mark local process as operational. In cluster mode, this
+   * event is propagated to all nodes.
+   */
+  void setOperational(ProcessId processId);
+
+  boolean tryToLockWebLeader();
+
+  void reset();
+
+  void registerSonarQubeVersion(String sonarqubeVersion);
+
+  Optional<String> getLeaderHostName();
+
+  @Override
+  void close();
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java b/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java
new file mode 100644 (file)
index 0000000..196aa2a
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.sonar.application.cluster.AppStateClusterImpl;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.ClusterSettings;
+
+public class AppStateFactory {
+
+  private final AppSettings settings;
+
+  public AppStateFactory(AppSettings settings) {
+    this.settings = settings;
+  }
+
+  public AppState create() {
+    return ClusterSettings.isClusterEnabled(settings) ? new AppStateClusterImpl(settings) : new AppStateImpl();
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppStateImpl.java b/server/sonar-main/src/main/java/org/sonar/application/AppStateImpl.java
new file mode 100644 (file)
index 0000000..9c5e03c
--- /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.application;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nonnull;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+
+public class AppStateImpl implements AppState {
+
+  private final Map<ProcessId, Boolean> processes = new EnumMap<>(ProcessId.class);
+  private final List<AppStateListener> listeners = new ArrayList<>();
+  private final AtomicBoolean webLeaderLocked = new AtomicBoolean(false);
+
+  @Override
+  public void addListener(@Nonnull AppStateListener listener) {
+    this.listeners.add(listener);
+  }
+
+  @Override
+  public boolean isOperational(ProcessId processId, boolean local) {
+    return processes.computeIfAbsent(processId, p -> false);
+  }
+
+  @Override
+  public void setOperational(ProcessId processId) {
+    processes.put(processId, true);
+    listeners.forEach(l -> l.onAppStateOperational(processId));
+  }
+
+  @Override
+  public boolean tryToLockWebLeader() {
+    return webLeaderLocked.compareAndSet(false, true);
+  }
+
+  @Override
+  public void reset() {
+    webLeaderLocked.set(false);
+    processes.clear();
+  }
+
+  @Override
+  public void registerSonarQubeVersion(String sonarqubeVersion) {
+    // Nothing to do on non clustered version
+  }
+
+  @Override
+  public Optional<String> getLeaderHostName() {
+    return Optional.of(NetworkUtils.getHostName());
+  }
+
+  @Override
+  public void close() {
+    // nothing to do
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppStateListener.java b/server/sonar-main/src/main/java/org/sonar/application/AppStateListener.java
new file mode 100644 (file)
index 0000000..581ea40
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import org.sonar.process.ProcessId;
+
+@FunctionalInterface
+public interface AppStateListener {
+
+  /**
+   * The method is called when the state is changed. When cluster
+   * mode is enabled, the event may be raised from another node.
+   *
+   * Listener must subscribe to {@link AppState#addListener(AppStateListener)}.
+   */
+  void onAppStateOperational(ProcessId processId);
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/FileSystem.java b/server/sonar-main/src/main/java/org/sonar/application/FileSystem.java
new file mode 100644 (file)
index 0000000..efdbcd2
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface FileSystem {
+
+  void reset() throws IOException;
+
+  File getTempDir();
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/NodeLifecycle.java b/server/sonar-main/src/main/java/org/sonar/application/NodeLifecycle.java
new file mode 100644 (file)
index 0000000..62e09a6
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.sonar.application.NodeLifecycle.State.INIT;
+import static org.sonar.application.NodeLifecycle.State.OPERATIONAL;
+import static org.sonar.application.NodeLifecycle.State.STARTING;
+import static org.sonar.application.NodeLifecycle.State.STOPPED;
+import static org.sonar.application.NodeLifecycle.State.STOPPING;
+
+/**
+ * Lifecycle of the cluster node, consolidating the states
+ * of child processes.
+ */
+class NodeLifecycle {
+  private static final Logger LOG = LoggerFactory.getLogger(NodeLifecycle.class);
+
+  enum State {
+    // initial state, does nothing
+    INIT,
+
+    // at least one process is still starting
+    STARTING,
+
+    // all the processes are started and operational
+    OPERATIONAL,
+
+    // at least one process is still stopping
+    STOPPING,
+
+    // all processes are stopped
+    STOPPED
+  }
+
+  private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
+
+  private State state = INIT;
+
+  private static Map<State, Set<State>> buildTransitions() {
+    Map<State, Set<State>> res = new EnumMap<>(State.class);
+    res.put(INIT, toSet(STARTING));
+    res.put(STARTING, toSet(OPERATIONAL, STOPPING, STOPPED));
+    res.put(OPERATIONAL, toSet(STOPPING, STOPPED));
+    res.put(STOPPING, toSet(STOPPED));
+    res.put(STOPPED, toSet(STARTING));
+    return res;
+  }
+
+  private static Set<State> toSet(State... states) {
+    if (states.length == 0) {
+      return Collections.emptySet();
+    }
+    if (states.length == 1) {
+      return Collections.singleton(states[0]);
+    }
+    return EnumSet.copyOf(Arrays.asList(states));
+  }
+
+  State getState() {
+    return state;
+  }
+
+  synchronized boolean tryToMoveTo(State to) {
+    boolean res = false;
+    State currentState = state;
+    if (TRANSITIONS.get(currentState).contains(to)) {
+      this.state = to;
+      res = true;
+    }
+    LOG.trace("tryToMoveTo from {} to {} => {}", currentState, to, res);
+    return res;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/Scheduler.java b/server/sonar-main/src/main/java/org/sonar/application/Scheduler.java
new file mode 100644 (file)
index 0000000..f69b61d
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+public interface Scheduler {
+
+  void schedule();
+
+  /**
+   * Stops all processes and waits for them to be down.
+   */
+  void terminate();
+
+  /**
+   * Blocks until all processes are down
+   */
+  void awaitTermination();
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java b/server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java
new file mode 100644 (file)
index 0000000..7311297
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * 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;
+
+import java.util.EnumMap;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.ClusterSettings;
+import org.sonar.process.command.CommandFactory;
+import org.sonar.process.command.EsCommand;
+import org.sonar.process.command.JavaCommand;
+import org.sonar.application.process.ProcessLauncher;
+import org.sonar.application.process.Lifecycle;
+import org.sonar.application.process.ProcessEventListener;
+import org.sonar.application.process.ProcessLifecycleListener;
+import org.sonar.application.process.ProcessMonitor;
+import org.sonar.application.process.SQProcess;
+import org.sonar.process.ProcessId;
+
+public class SchedulerImpl implements Scheduler, ProcessEventListener, ProcessLifecycleListener, AppStateListener {
+
+  private static final Logger LOG = LoggerFactory.getLogger(SchedulerImpl.class);
+
+  private final AppSettings settings;
+  private final AppReloader appReloader;
+  private final CommandFactory commandFactory;
+  private final ProcessLauncher processLauncher;
+  private final AppState appState;
+  private final NodeLifecycle nodeLifecycle = new NodeLifecycle();
+
+  private final CountDownLatch keepAlive = new CountDownLatch(1);
+  private final AtomicBoolean restartRequested = new AtomicBoolean(false);
+  private final AtomicBoolean restartDisabled = new AtomicBoolean(false);
+  private final EnumMap<ProcessId, SQProcess> processesById = new EnumMap<>(ProcessId.class);
+  private final AtomicInteger operationalCountDown = new AtomicInteger();
+  private final AtomicInteger stopCountDown = new AtomicInteger(0);
+  private StopperThread stopperThread;
+  private RestarterThread restarterThread;
+  private long processWatcherDelayMs = SQProcess.DEFAULT_WATCHER_DELAY_MS;
+
+  public SchedulerImpl(AppSettings settings, AppReloader appReloader, CommandFactory commandFactory,
+    ProcessLauncher processLauncher,
+    AppState appState) {
+    this.settings = settings;
+    this.appReloader = appReloader;
+    this.commandFactory = commandFactory;
+    this.processLauncher = processLauncher;
+    this.appState = appState;
+    this.appState.addListener(this);
+  }
+
+  SchedulerImpl setProcessWatcherDelayMs(long l) {
+    this.processWatcherDelayMs = l;
+    return this;
+  }
+
+  @Override
+  public void schedule() {
+    if (!nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STARTING)) {
+      return;
+    }
+    processesById.clear();
+
+    for (ProcessId processId : ClusterSettings.getEnabledProcesses(settings)) {
+      SQProcess process = SQProcess.builder(processId)
+        .addProcessLifecycleListener(this)
+        .addEventListener(this)
+        .setWatcherDelayMs(processWatcherDelayMs)
+        .build();
+      processesById.put(process.getProcessId(), process);
+    }
+    operationalCountDown.set(processesById.size());
+
+    tryToStartAll();
+  }
+
+  private void tryToStartAll() {
+    tryToStartEs();
+    tryToStartWeb();
+    tryToStartCe();
+  }
+
+  private void tryToStartEs() {
+    SQProcess process = processesById.get(ProcessId.ELASTICSEARCH);
+    if (process != null) {
+      tryToStartEsProcess(process, commandFactory::createEsCommand);
+    }
+  }
+
+  private void tryToStartWeb() {
+    SQProcess process = processesById.get(ProcessId.WEB_SERVER);
+    if (process == null || !isEsClientStartable()) {
+      return;
+    }
+    if (appState.isOperational(ProcessId.WEB_SERVER, false)) {
+      tryToStartJavaProcess(process, () -> commandFactory.createWebCommand(false));
+    } else if (appState.tryToLockWebLeader()) {
+      tryToStartJavaProcess(process, () -> commandFactory.createWebCommand(true));
+    } else {
+      Optional<String> leader = appState.getLeaderHostName();
+      if (leader.isPresent()) {
+        LOG.info("Waiting for initialization from " + leader.get());
+      } else {
+        LOG.error("Initialization failed. All nodes must be restarted");
+      }
+    }
+  }
+
+  private void tryToStartCe() {
+    SQProcess process = processesById.get(ProcessId.COMPUTE_ENGINE);
+    if (process != null && appState.isOperational(ProcessId.WEB_SERVER, false) && isEsClientStartable()) {
+      tryToStartJavaProcess(process, commandFactory::createCeCommand);
+    }
+  }
+
+  private boolean isEsClientStartable() {
+    boolean requireLocalEs = ClusterSettings.isLocalElasticsearchEnabled(settings);
+    return appState.isOperational(ProcessId.ELASTICSEARCH, requireLocalEs);
+  }
+
+  private void tryToStartJavaProcess(SQProcess process, Supplier<JavaCommand> commandSupplier) {
+    tryToStart(process, () -> {
+      JavaCommand command = commandSupplier.get();
+      return processLauncher.launch(command);
+    });
+  }
+
+  private void tryToStartEsProcess(SQProcess process, Supplier<EsCommand> commandSupplier) {
+    tryToStart(process, () -> {
+      EsCommand command = commandSupplier.get();
+      return processLauncher.launch(command);
+    });
+  }
+
+  private void tryToStart(SQProcess process, Supplier<ProcessMonitor> processMonitorSupplier) {
+    try {
+      process.start(processMonitorSupplier);
+    } catch (RuntimeException e) {
+      // failed to start command -> stop everything
+      terminate();
+      throw e;
+    }
+  }
+
+  private void stopAll() {
+    // order is important for non-cluster mode
+    stopProcess(ProcessId.COMPUTE_ENGINE);
+    stopProcess(ProcessId.WEB_SERVER);
+    stopProcess(ProcessId.ELASTICSEARCH);
+  }
+
+  /**
+   * Request for graceful stop then blocks until process is stopped.
+   * Returns immediately if the process is disabled in configuration.
+   */
+  private void stopProcess(ProcessId processId) {
+    SQProcess process = processesById.get(processId);
+    if (process != null) {
+      process.stop(1, TimeUnit.MINUTES);
+    }
+  }
+
+  /**
+   * Blocks until all processes are stopped. Pending restart, if
+   * any, is disabled.
+   */
+  @Override
+  public void terminate() {
+    // disable ability to request for restart
+    restartRequested.set(false);
+    restartDisabled.set(true);
+
+    if (nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STOPPING)) {
+      LOG.info("Stopping SonarQube");
+    }
+    stopAll();
+    if (stopperThread != null) {
+      stopperThread.interrupt();
+    }
+    if (restarterThread != null) {
+      restarterThread.interrupt();
+    }
+    keepAlive.countDown();
+  }
+
+  @Override
+  public void awaitTermination() {
+    try {
+      keepAlive.await();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  @Override
+  public void onProcessEvent(ProcessId processId, Type type) {
+    if (type == Type.OPERATIONAL) {
+      onProcessOperational(processId);
+    } else if (type == Type.ASK_FOR_RESTART && restartRequested.compareAndSet(false, true)) {
+      stopAsync();
+    }
+  }
+
+  private void onProcessOperational(ProcessId processId) {
+    LOG.info("Process[{}] is up", processId.getKey());
+    appState.setOperational(processId);
+    if (operationalCountDown.decrementAndGet() == 0 && nodeLifecycle.tryToMoveTo(NodeLifecycle.State.OPERATIONAL)) {
+      LOG.info("SonarQube is up");
+    }
+  }
+
+  @Override
+  public void onAppStateOperational(ProcessId processId) {
+    if (nodeLifecycle.getState() == NodeLifecycle.State.STARTING) {
+      tryToStartAll();
+    }
+  }
+
+  @Override
+  public void onProcessState(ProcessId processId, Lifecycle.State to) {
+    switch (to) {
+      case STOPPED:
+        onProcessStop(processId);
+        break;
+      case STARTING:
+        stopCountDown.incrementAndGet();
+        break;
+      default:
+        // Nothing to do
+        break;
+    }
+  }
+
+  private void onProcessStop(ProcessId processId) {
+    LOG.info("Process [{}] is stopped", processId.getKey());
+    if (stopCountDown.decrementAndGet() == 0 && nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STOPPED)) {
+      if (!restartDisabled.get() &&
+        restartRequested.compareAndSet(true, false)) {
+        LOG.info("SonarQube is restarting");
+        restartAsync();
+      } else {
+        LOG.info("SonarQube is stopped");
+        // all processes are stopped, no restart requested
+        // Let's clean-up resources
+        terminate();
+      }
+
+    } else if (nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STOPPING)) {
+      // this is the first process stopping
+      stopAsync();
+    }
+  }
+
+  private void stopAsync() {
+    stopperThread = new StopperThread();
+    stopperThread.start();
+  }
+
+  private void restartAsync() {
+    restarterThread = new RestarterThread();
+    restarterThread.start();
+  }
+
+  private class RestarterThread extends Thread {
+    public RestarterThread() {
+      super("Restarter");
+    }
+
+    @Override
+    public void run() {
+      try {
+        appReloader.reload(settings);
+        schedule();
+      } catch (Exception e) {
+        LOG.error("Fail to restart", e);
+        terminate();
+      }
+    }
+  }
+
+  private class StopperThread extends Thread {
+    public StopperThread() {
+      super("Stopper");
+    }
+
+    @Override
+    public void run() {
+      stopAll();
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java
new file mode 100644 (file)
index 0000000..5e5f2b0
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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.cluster;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.AppState;
+import org.sonar.application.AppStateListener;
+import org.sonar.application.config.AppSettings;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+
+public class AppStateClusterImpl implements AppState {
+  private static Logger LOGGER = LoggerFactory.getLogger(AppStateClusterImpl.class);
+
+  private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class);
+  private final HazelcastCluster hazelcastCluster;
+
+  public AppStateClusterImpl(AppSettings appSettings) {
+    ClusterProperties clusterProperties = new ClusterProperties(appSettings);
+    clusterProperties.validate();
+
+    if (!clusterProperties.isEnabled()) {
+      throw new IllegalStateException("Cluster is not enabled on this instance");
+    }
+
+    hazelcastCluster = HazelcastCluster.create(clusterProperties);
+    // Add the local endpoint to be used by processes
+    appSettings.getProps().set(ProcessProperties.CLUSTER_LOCALENDPOINT, hazelcastCluster.getLocalEndPoint());
+    appSettings.getProps().set(ProcessProperties.CLUSTER_MEMBERUUID, hazelcastCluster.getLocalUUID());
+
+    String members = hazelcastCluster.getMembers().stream().collect(Collectors.joining(","));
+    LOGGER.info("Joined the cluster [{}] that contains the following hosts : [{}]", hazelcastCluster.getName(), members);
+  }
+
+  @Override
+  public void addListener(@Nonnull AppStateListener listener) {
+    hazelcastCluster.addListener(listener);
+  }
+
+  @Override
+  public boolean isOperational(@Nonnull ProcessId processId, boolean local) {
+    if (local) {
+      return localProcesses.computeIfAbsent(processId, p -> false);
+    }
+    return hazelcastCluster.isOperational(processId);
+  }
+
+  @Override
+  public void setOperational(@Nonnull ProcessId processId) {
+    localProcesses.put(processId, true);
+    hazelcastCluster.setOperational(processId);
+  }
+
+  @Override
+  public boolean tryToLockWebLeader() {
+    return hazelcastCluster.tryToLockWebLeader();
+  }
+
+  @Override
+  public void reset() {
+    throw new IllegalStateException("state reset is not supported in cluster mode");
+  }
+
+  @Override
+  public void close() {
+    hazelcastCluster.close();
+  }
+
+  @Override
+  public void registerSonarQubeVersion(String sonarqubeVersion) {
+    hazelcastCluster.registerSonarQubeVersion(sonarqubeVersion);
+  }
+
+  @Override
+  public Optional<String> getLeaderHostName() {
+    return hazelcastCluster.getLeaderHostName();
+  }
+
+  HazelcastCluster getHazelcastCluster() {
+    return hazelcastCluster;
+  }
+
+  /**
+   * Only used for testing purpose
+   *
+   * @param logger
+   */
+  static void setLogger(Logger logger) {
+    AppStateClusterImpl.LOGGER = logger;
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProcess.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProcess.java
new file mode 100644 (file)
index 0000000..b9b3340
--- /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.application.cluster;
+
+import java.io.Serializable;
+import org.sonar.process.ProcessId;
+
+import static java.util.Objects.requireNonNull;
+
+public class ClusterProcess implements Serializable {
+  private final ProcessId processId;
+  private final String nodeUuid;
+
+  public ClusterProcess(String nodeUuid, ProcessId processId) {
+    this.processId = requireNonNull(processId);
+    this.nodeUuid = requireNonNull(nodeUuid);
+  }
+
+  public ProcessId getProcessId() {
+    return processId;
+  }
+
+  public String getNodeUuid() {
+    return nodeUuid;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ClusterProcess that = (ClusterProcess) o;
+    if (processId != that.processId) {
+      return false;
+    }
+    return nodeUuid.equals(that.nodeUuid);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = processId.hashCode();
+    result = 31 * result + nodeUuid.hashCode();
+    return result;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java
new file mode 100644 (file)
index 0000000..f33877f
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * 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.cluster;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.config.AppSettings;
+import org.sonar.process.ProcessProperties;
+
+/**
+ * Properties of the cluster configuration
+ */
+public final class ClusterProperties {
+  static final String DEFAULT_PORT = "9003";
+  private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class);
+
+  private final int port;
+  private final boolean enabled;
+  private final List<String> hosts;
+  private final List<String> networkInterfaces;
+  private final String name;
+
+  ClusterProperties(AppSettings appSettings) {
+    port = appSettings.getProps().valueAsInt(ProcessProperties.CLUSTER_PORT);
+    enabled = appSettings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_ENABLED);
+    networkInterfaces = extractNetworkInterfaces(
+      appSettings.getProps().value(ProcessProperties.CLUSTER_NETWORK_INTERFACES, "")
+    );
+    name = appSettings.getProps().nonNullValue(ProcessProperties.CLUSTER_NAME);
+    hosts = extractHosts(
+      appSettings.getProps().value(ProcessProperties.CLUSTER_HOSTS, "")
+    );
+  }
+
+  int getPort() {
+    return port;
+  }
+
+  boolean isEnabled() {
+    return enabled;
+  }
+
+  List<String> getHosts() {
+    return hosts;
+  }
+
+  List<String> getNetworkInterfaces() {
+    return networkInterfaces;
+  }
+
+  String getName() {
+    return name;
+  }
+
+  void validate() {
+    if (!enabled) {
+      return;
+    }
+
+    // Test validity of port
+    checkArgument(
+      port > 0 && port < 65_536,
+      "Cluster port have been set to %d which is outside the range [1-65535].",
+      port
+    );
+
+    // Test the networkInterfaces parameter
+    try {
+      List<String> localInterfaces = findAllLocalIPs();
+
+      networkInterfaces.forEach(
+        inet -> checkArgument(
+          StringUtils.isEmpty(inet) || localInterfaces.contains(inet),
+          "Interface %s is not available on this machine.",
+          inet
+        )
+      );
+    } catch (SocketException e) {
+      LOGGER.warn("Unable to retrieve network networkInterfaces. Interfaces won't be checked", e);
+    }
+  }
+
+  private static List<String> extractHosts(final String hosts) {
+    List<String> result = new ArrayList<>();
+    for (String host : hosts.split(",")) {
+      if (StringUtils.isNotEmpty(host)) {
+        if (!host.contains(":")) {
+          result.add(
+            String.format("%s:%s", host, DEFAULT_PORT)
+          );
+        } else {
+          result.add(host);
+        }
+      }
+    }
+    return result;
+  }
+
+  private static List<String> extractNetworkInterfaces(final String networkInterfaces) {
+    List<String> result = new ArrayList<>();
+    for (String iface : networkInterfaces.split(",")) {
+      if (StringUtils.isNotEmpty(iface)) {
+        result.add(iface);
+      }
+    }
+    return result;
+  }
+
+  private static List<String> findAllLocalIPs() throws SocketException {
+    Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
+    List<String> localInterfaces = new ArrayList<>();
+
+    while (netInterfaces.hasMoreElements()) {
+      NetworkInterface networkInterface = netInterfaces.nextElement();
+      Enumeration<InetAddress> ips = networkInterface.getInetAddresses();
+      while (ips.hasMoreElements()) {
+        InetAddress ip = ips.nextElement();
+        localInterfaces.add(ip.getHostAddress());
+      }
+    }
+    return localInterfaces;
+  }
+
+  private static void checkArgument(boolean expression,
+                                    @Nullable String messageTemplate,
+                                    @Nullable Object... args) {
+    if (!expression) {
+      throw new IllegalArgumentException(String.format(messageTemplate, args));
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/HazelcastCluster.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/HazelcastCluster.java
new file mode 100644 (file)
index 0000000..a1e4f77
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * 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.cluster;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.config.NetworkConfig;
+import com.hazelcast.core.Client;
+import com.hazelcast.core.ClientListener;
+import com.hazelcast.core.EntryEvent;
+import com.hazelcast.core.EntryListener;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.core.HazelcastInstanceNotActiveException;
+import com.hazelcast.core.IAtomicReference;
+import com.hazelcast.core.ILock;
+import com.hazelcast.core.MapEvent;
+import com.hazelcast.core.Member;
+import com.hazelcast.core.ReplicatedMap;
+import com.hazelcast.nio.Address;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.AppStateListener;
+import org.sonar.process.ProcessId;
+
+import static java.util.stream.Collectors.toList;
+import static org.sonar.process.NetworkUtils.getHostName;
+import static org.sonar.process.cluster.ClusterObjectKeys.CLIENT_UUIDS;
+import static org.sonar.process.cluster.ClusterObjectKeys.HOSTNAME;
+import static org.sonar.process.cluster.ClusterObjectKeys.LEADER;
+import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
+import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
+
+public class HazelcastCluster implements AutoCloseable {
+  private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastCluster.class);
+
+  private final List<AppStateListener> listeners = new ArrayList<>();
+  private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
+  private final String operationalProcessListenerUUID;
+  private final String clientListenerUUID;
+
+  protected final HazelcastInstance hzInstance;
+
+  private HazelcastCluster(Config hzConfig) {
+    // Create the Hazelcast instance
+    hzInstance = Hazelcast.newHazelcastInstance(hzConfig);
+
+    // Get or create the replicated map
+    operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
+    operationalProcessListenerUUID = operationalProcesses.addEntryListener(new OperationalProcessListener());
+    clientListenerUUID = hzInstance.getClientService().addClientListener(new ConnectedClientListener());
+  }
+
+  String getLocalUUID() {
+    return hzInstance.getLocalEndpoint().getUuid();
+  }
+
+  String getName() {
+    return hzInstance.getConfig().getGroupConfig().getName();
+  }
+
+  List<String> getMembers() {
+    return hzInstance.getCluster().getMembers().stream()
+      .filter(m -> !m.localMember())
+      .map(m -> m.getStringAttribute(HOSTNAME))
+      .collect(toList());
+  }
+
+  void addListener(AppStateListener listener) {
+    listeners.add(listener);
+  }
+
+  boolean isOperational(ProcessId processId) {
+    for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
+      if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void setOperational(ProcessId processId) {
+    operationalProcesses.put(new ClusterProcess(getLocalUUID(), processId), Boolean.TRUE);
+  }
+
+  boolean tryToLockWebLeader() {
+    IAtomicReference<String> leader = hzInstance.getAtomicReference(LEADER);
+    if (leader.get() == null) {
+      ILock lock = hzInstance.getLock(LEADER);
+      lock.lock();
+      try {
+        if (leader.get() == null) {
+          leader.set(getLocalUUID());
+          return true;
+        } else {
+          return false;
+        }
+      } finally {
+        lock.unlock();
+      }
+    } else {
+      return false;
+    }
+  }
+
+  public void registerSonarQubeVersion(String sonarqubeVersion) {
+    IAtomicReference<String> sqVersion = hzInstance.getAtomicReference(SONARQUBE_VERSION);
+    if (sqVersion.get() == null) {
+      ILock lock = hzInstance.getLock(SONARQUBE_VERSION);
+      lock.lock();
+      try {
+        if (sqVersion.get() == null) {
+          sqVersion.set(sonarqubeVersion);
+        }
+      } finally {
+        lock.unlock();
+      }
+    }
+
+    String clusterVersion = sqVersion.get();
+    if (!sqVersion.get().equals(sonarqubeVersion)) {
+      throw new IllegalStateException(
+        String.format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion)
+      );
+    }
+  }
+
+  @Override
+  public void close() {
+    if (hzInstance != null) {
+      try {
+        // Removing listeners
+        operationalProcesses.removeEntryListener(operationalProcessListenerUUID);
+        hzInstance.getClientService().removeClientListener(clientListenerUUID);
+
+        // Removing the operationalProcess from the replicated map
+        operationalProcesses.keySet().forEach(
+          clusterNodeProcess -> {
+            if (clusterNodeProcess.getNodeUuid().equals(getLocalUUID())) {
+              operationalProcesses.remove(clusterNodeProcess);
+            }
+          });
+
+        // Shutdown Hazelcast properly
+        hzInstance.shutdown();
+      } catch (HazelcastInstanceNotActiveException e) {
+        // hazelcastCluster may be already closed by the shutdown hook
+        LOGGER.debug("Unable to close Hazelcast cluster", e);
+      }
+    }
+  }
+
+  public static HazelcastCluster create(ClusterProperties clusterProperties) {
+    Config hzConfig = new Config();
+    hzConfig.getGroupConfig().setName(clusterProperties.getName());
+
+    // Configure the network instance
+    NetworkConfig netConfig = hzConfig.getNetworkConfig();
+    netConfig
+      .setPort(clusterProperties.getPort())
+      .setReuseAddress(true);
+
+    if (!clusterProperties.getNetworkInterfaces().isEmpty()) {
+      netConfig.getInterfaces()
+        .setEnabled(true)
+        .setInterfaces(clusterProperties.getNetworkInterfaces());
+    }
+
+    // Only allowing TCP/IP configuration
+    JoinConfig joinConfig = netConfig.getJoin();
+    joinConfig.getAwsConfig().setEnabled(false);
+    joinConfig.getMulticastConfig().setEnabled(false);
+    joinConfig.getTcpIpConfig().setEnabled(true);
+    joinConfig.getTcpIpConfig().setMembers(clusterProperties.getHosts());
+
+    // Tweak HazelCast configuration
+    hzConfig
+      // Increase the number of tries
+      .setProperty("hazelcast.tcp.join.port.try.count", "10")
+      // Don't bind on all interfaces
+      .setProperty("hazelcast.socket.bind.any", "false")
+      // Don't phone home
+      .setProperty("hazelcast.phone.home.enabled", "false")
+      // Use slf4j for logging
+      .setProperty("hazelcast.logging.type", "slf4j");
+
+    // Trying to resolve the hostname
+    hzConfig.getMemberAttributeConfig().setStringAttribute(HOSTNAME, getHostName());
+
+    // We are not using the partition group of Hazelcast, so disabling it
+    hzConfig.getPartitionGroupConfig().setEnabled(false);
+    return new HazelcastCluster(hzConfig);
+  }
+
+  Optional<String> getLeaderHostName() {
+    String leaderId = (String) hzInstance.getAtomicReference(LEADER).get();
+    if (leaderId != null) {
+      Optional<Member> leader = hzInstance.getCluster().getMembers().stream().filter(m -> m.getUuid().equals(leaderId)).findFirst();
+      if (leader.isPresent()) {
+        return Optional.of(leader.get().getStringAttribute(HOSTNAME));
+      }
+    }
+    return Optional.empty();
+  }
+
+  String getLocalEndPoint() {
+    Address localAddress = hzInstance.getCluster().getLocalMember().getAddress();
+    return String.format("%s:%d", localAddress.getHost(), localAddress.getPort());
+  }
+
+  private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
+    @Override
+    public void entryAdded(EntryEvent<ClusterProcess, Boolean> event) {
+      if (event.getValue()) {
+        listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
+      }
+    }
+
+    @Override
+    public void entryRemoved(EntryEvent<ClusterProcess, Boolean> event) {
+      // Ignore it
+    }
+
+    @Override
+    public void entryUpdated(EntryEvent<ClusterProcess, Boolean> event) {
+      if (event.getValue()) {
+        listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
+      }
+    }
+
+    @Override
+    public void entryEvicted(EntryEvent<ClusterProcess, Boolean> event) {
+      // Ignore it
+    }
+
+    @Override
+    public void mapCleared(MapEvent event) {
+      // Ignore it
+    }
+
+    @Override
+    public void mapEvicted(MapEvent event) {
+      // Ignore it
+    }
+  }
+
+  private class ConnectedClientListener implements ClientListener {
+    @Override
+    public void clientConnected(Client client) {
+      hzInstance.getSet(CLIENT_UUIDS).add(client.getUuid());
+    }
+
+    @Override
+    public void clientDisconnected(Client client) {
+      hzInstance.getSet(CLIENT_UUIDS).remove(client.getUuid());
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/package-info.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/package-info.java
new file mode 100644 (file)
index 0000000..4501fba
--- /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.cluster;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/AppSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/AppSettings.java
new file mode 100644 (file)
index 0000000..784e7b1
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.config;
+
+import java.util.Optional;
+import org.sonar.process.Props;
+
+public interface AppSettings {
+
+  Props getProps();
+
+  Optional<String> getValue(String key);
+
+  void reload(Props copy);
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsImpl.java b/server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsImpl.java
new file mode 100644 (file)
index 0000000..02828a7
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.config;
+
+import java.util.Optional;
+import org.sonar.process.Props;
+
+public class AppSettingsImpl implements AppSettings {
+
+  private Props props;
+
+  AppSettingsImpl(Props props) {
+    this.props = props;
+  }
+
+  @Override
+  public Props getProps() {
+    return props;
+  }
+
+  @Override
+  public Optional<String> getValue(String key) {
+    return Optional.ofNullable(props.value(key));
+  }
+
+  @Override
+  public void reload(Props copy) {
+    this.props = copy;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsLoader.java b/server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsLoader.java
new file mode 100644 (file)
index 0000000..5362c05
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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.config;
+
+public interface AppSettingsLoader {
+
+  AppSettings load();
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java b/server/sonar-main/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java
new file mode 100644 (file)
index 0000000..50436b6
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.function.Consumer;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ConfigurationUtils;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class AppSettingsLoaderImpl implements AppSettingsLoader {
+
+  private final File homeDir;
+  private final String[] cliArguments;
+  private final Consumer<Props>[] consumers;
+
+  public AppSettingsLoaderImpl(String[] cliArguments) {
+    this(cliArguments, detectHomeDir(), new FileSystemSettings(), new JdbcSettings(), new ClusterSettings());
+  }
+
+  AppSettingsLoaderImpl(String[] cliArguments, File homeDir, Consumer<Props>... consumers) {
+    this.cliArguments = cliArguments;
+    this.homeDir = homeDir;
+    this.consumers = consumers;
+  }
+
+  File getHomeDir() {
+    return homeDir;
+  }
+
+  @Override
+  public AppSettings load() {
+    Properties p = loadPropertiesFile(homeDir);
+    p.putAll(CommandLineParser.parseArguments(cliArguments));
+    p.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
+    p = ConfigurationUtils.interpolateVariables(p, System.getenv());
+
+    // the difference between Properties and Props is that the latter
+    // supports decryption of values, so it must be used when values
+    // are accessed
+    Props props = new Props(p);
+    ProcessProperties.completeDefaults(props);
+    Arrays.stream(consumers).forEach(c -> c.accept(props));
+
+    return new AppSettingsImpl(props);
+  }
+
+  private static File detectHomeDir() {
+    try {
+      File appJar = new File(AppSettingsLoaderImpl.class.getProtectionDomain().getCodeSource().getLocation().toURI());
+      return appJar.getParentFile().getParentFile();
+    } catch (URISyntaxException e) {
+      throw new IllegalStateException("Cannot detect path of main jar file", e);
+    }
+  }
+
+  /**
+   * Loads the configuration file ${homeDir}/conf/sonar.properties.
+   * An empty {@link Properties} is returned if the file does not exist.
+   */
+  private static Properties loadPropertiesFile(File homeDir) {
+    Properties p = new Properties();
+    File propsFile = new File(homeDir, "conf/sonar.properties");
+    if (propsFile.exists()) {
+      try (Reader reader = new InputStreamReader(new FileInputStream(propsFile), UTF_8)) {
+        p.load(reader);
+      } catch (IOException e) {
+        throw new IllegalStateException("Cannot open file " + propsFile, e);
+      }
+    } else {
+      LoggerFactory.getLogger(AppSettingsLoaderImpl.class).warn("Configuration file not found: {}", propsFile);
+    }
+    return p;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
new file mode 100644 (file)
index 0000000..b6b8ce3
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * 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.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.net.HostAndPort;
+import com.google.common.net.InetAddresses;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static com.google.common.net.InetAddresses.forString;
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NETWORK_INTERFACES;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_WEB_LEADER;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+import static org.sonar.process.ProcessProperties.SEARCH_HOST;
+
+public class ClusterSettings implements Consumer<Props> {
+
+  @Override
+  public void accept(Props props) {
+    if (!isClusterEnabled(props)) {
+      return;
+    }
+
+    checkProperties(props);
+  }
+
+  private static void checkProperties(Props props) {
+    // Cluster web leader is not allowed
+    if (props.value(CLUSTER_WEB_LEADER) != null) {
+      throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_LEADER));
+    }
+
+    if (props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED) &&
+      !props.valueAsBoolean(ProcessProperties.CLUSTER_SEARCH_DISABLED, false)
+      ) {
+      ensureMandatoryProperty(props, SEARCH_HOST);
+      ensureNotLoopback(props, SEARCH_HOST);
+    }
+    // Mandatory properties
+    ensureMandatoryProperty(props, CLUSTER_HOSTS);
+    ensureMandatoryProperty(props, CLUSTER_SEARCH_HOSTS);
+
+    // H2 Database is not allowed in cluster mode
+    String jdbcUrl = props.value(JDBC_URL);
+    if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) {
+      throw new MessageException("Embedded database is not supported in cluster mode");
+    }
+
+    // Loopback interfaces are forbidden for SEARCH_HOST and CLUSTER_NETWORK_INTERFACES
+    ensureNotLoopback(props, CLUSTER_HOSTS);
+    ensureNotLoopback(props, CLUSTER_NETWORK_INTERFACES);
+    ensureNotLoopback(props, CLUSTER_SEARCH_HOSTS);
+
+    ensureLocalAddress(props, SEARCH_HOST);
+    ensureLocalAddress(props, CLUSTER_NETWORK_INTERFACES);
+  }
+
+  private static void ensureMandatoryProperty(Props props, String key) {
+    if (isBlank(props.value(key))) {
+      throw new MessageException(format("Property [%s] is mandatory", key));
+    }
+  }
+
+  @VisibleForTesting
+  protected static void ensureNotLoopback(Props props, String key) {
+    String ipList = props.value(key);
+    if (ipList == null) {
+      return;
+    }
+
+    stream(ipList.split(","))
+      .filter(StringUtils::isNotBlank)
+      .map(StringUtils::trim)
+      .forEach(ip -> {
+        InetAddress inetAddress = convertToInetAddress(ip, key);
+        if (inetAddress.isLoopbackAddress()) {
+          throw new MessageException(format("The interface address [%s] of [%s] must not be a loopback address", ip, key));
+        }
+      });
+  }
+
+  private static void ensureLocalAddress(Props props, String key) {
+    String ipList = props.value(key);
+
+    if (ipList == null) {
+      return;
+    }
+
+    stream(ipList.split(","))
+      .filter(StringUtils::isNotBlank)
+      .map(StringUtils::trim)
+      .forEach(ip -> {
+        InetAddress inetAddress = convertToInetAddress(ip, key);
+        try {
+          if (NetworkInterface.getByInetAddress(inetAddress) == null) {
+            throw new MessageException(format("The interface address [%s] of [%s] is not a local address", ip, key));
+          }
+        } catch (SocketException e) {
+          throw new MessageException(format("The interface address [%s] of [%s] is not a local address", ip, key));
+        }
+      });
+  }
+
+  private static InetAddress convertToInetAddress(String text, String key) {
+    InetAddress inetAddress;
+    HostAndPort hostAndPort = HostAndPort.fromString(text);
+    if (!InetAddresses.isInetAddress(hostAndPort.getHostText())) {
+      try {
+        inetAddress =InetAddress.getByName(hostAndPort.getHostText());
+      } catch (UnknownHostException e) {
+        throw new MessageException(format("The interface address [%s] of [%s] cannot be resolved : %s", text, key, e.getMessage()));
+      }
+    } else {
+      inetAddress = forString(hostAndPort.getHostText());
+    }
+
+    return inetAddress;
+  }
+
+  public static boolean isClusterEnabled(AppSettings settings) {
+    return isClusterEnabled(settings.getProps());
+  }
+
+  private static boolean isClusterEnabled(Props props) {
+    return props.valueAsBoolean(CLUSTER_ENABLED);
+  }
+
+  public static List<ProcessId> getEnabledProcesses(AppSettings settings) {
+    if (!isClusterEnabled(settings)) {
+      return Arrays.asList(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
+    }
+    List<ProcessId> enabled = new ArrayList<>();
+    if (!settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_SEARCH_DISABLED)) {
+      enabled.add(ProcessId.ELASTICSEARCH);
+    }
+    if (!settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_WEB_DISABLED)) {
+      enabled.add(ProcessId.WEB_SERVER);
+    }
+
+    if (!settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_CE_DISABLED)) {
+      enabled.add(ProcessId.COMPUTE_ENGINE);
+    }
+    return enabled;
+  }
+
+  public static boolean isLocalElasticsearchEnabled(AppSettings settings) {
+    return !isClusterEnabled(settings.getProps()) ||
+      !settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_SEARCH_DISABLED);
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/CommandLineParser.java b/server/sonar-main/src/main/java/org/sonar/application/config/CommandLineParser.java
new file mode 100644 (file)
index 0000000..14422fb
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.config;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Map;
+import java.util.Properties;
+
+public class CommandLineParser {
+
+  private CommandLineParser() {
+    // prevent instantiation
+  }
+
+  /**
+   * Build properties from command-line arguments and system properties
+   */
+  public static Properties parseArguments(String[] args) {
+    Properties props = argumentsToProperties(args);
+
+    // complete with only the system properties that start with "sonar."
+    for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
+      String key = entry.getKey().toString();
+      if (key.startsWith("sonar.")) {
+        props.setProperty(key, entry.getValue().toString());
+      }
+    }
+    return props;
+  }
+
+  /**
+   * Convert strings "-Dkey=value" to properties
+   */
+  static Properties argumentsToProperties(String[] args) {
+    Properties props = new Properties();
+    for (String arg : args) {
+      if (!arg.startsWith("-D") || !arg.contains("=")) {
+        throw new IllegalArgumentException(String.format(
+          "Command-line argument must start with -D, for example -Dsonar.jdbc.username=sonar. Got: %s", arg));
+      }
+      String key = StringUtils.substringBefore(arg, "=").substring(2);
+      String value = StringUtils.substringAfter(arg, "=");
+      props.setProperty(key, value);
+    }
+    return props;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/FileSystemSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/FileSystemSettings.java
new file mode 100644 (file)
index 0000000..b7953ac
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.config;
+
+import java.io.File;
+import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.Props;
+
+import static org.sonar.process.ProcessProperties.PATH_DATA;
+import static org.sonar.process.ProcessProperties.PATH_HOME;
+import static org.sonar.process.ProcessProperties.PATH_LOGS;
+import static org.sonar.process.ProcessProperties.PATH_TEMP;
+import static org.sonar.process.ProcessProperties.PATH_WEB;
+
+public class FileSystemSettings implements Consumer<Props> {
+
+  private static final Logger LOG = LoggerFactory.getLogger(FileSystemSettings.class);
+
+  @Override
+  public void accept(Props props) {
+    ensurePropertyIsAbsolutePath(props, PATH_DATA);
+    ensurePropertyIsAbsolutePath(props, PATH_WEB);
+    ensurePropertyIsAbsolutePath(props, PATH_LOGS);
+    ensurePropertyIsAbsolutePath(props, PATH_TEMP);
+  }
+
+  private static File ensurePropertyIsAbsolutePath(Props props, String propKey) {
+    File homeDir = props.nonNullValueAsFile(PATH_HOME);
+    // default values are set by ProcessProperties
+    String path = props.nonNullValue(propKey);
+    File d = new File(path);
+    if (!d.isAbsolute()) {
+      d = new File(homeDir, path);
+      LOG.trace("Overriding property {} from relative path '{}' to absolute path '{}'", path, d.getAbsolutePath());
+      props.set(propKey, d.getAbsolutePath());
+    }
+    return d;
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/JdbcSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/JdbcSettings.java
new file mode 100644 (file)
index 0000000..9011f16
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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.config;
+
+import java.io.File;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.isEmpty;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+import static org.sonar.process.ProcessProperties.JDBC_EMBEDDED_PORT;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+
+public class JdbcSettings implements Consumer<Props> {
+
+  private static final int JDBC_EMBEDDED_PORT_DEFAULT_VALUE = 9092;
+
+  enum Provider {
+    H2("lib/jdbc/h2"), SQLSERVER("lib/jdbc/mssql"), MYSQL("lib/jdbc/mysql"), ORACLE("extensions/jdbc-driver/oracle"),
+    POSTGRESQL("lib/jdbc/postgresql");
+
+    final String path;
+
+    Provider(String path) {
+      this.path = path;
+    }
+  }
+
+  @Override
+  public void accept(Props props) {
+    File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
+    Provider provider = resolveProviderAndEnforceNonnullJdbcUrl(props);
+    String url = props.value(JDBC_URL);
+    checkUrlParameters(provider, url);
+    String driverPath = driverPath(homeDir, provider);
+    props.set(ProcessProperties.JDBC_DRIVER_PATH, driverPath);
+  }
+
+  String driverPath(File homeDir, Provider provider) {
+    String dirPath = provider.path;
+    File dir = new File(homeDir, dirPath);
+    if (!dir.exists()) {
+      throw new MessageException("Directory does not exist: " + dirPath);
+    }
+    List<File> files = new ArrayList<>(FileUtils.listFiles(dir, new String[] {"jar"}, false));
+    if (files.isEmpty()) {
+      throw new MessageException("Directory does not contain JDBC driver: " + dirPath);
+    }
+    if (files.size() > 1) {
+      throw new MessageException("Directory must contain only one JAR file: " + dirPath);
+    }
+    return files.get(0).getAbsolutePath();
+  }
+
+  Provider resolveProviderAndEnforceNonnullJdbcUrl(Props props) {
+    String url = props.value(JDBC_URL);
+    Integer embeddedDatabasePort = props.valueAsInt(JDBC_EMBEDDED_PORT);
+
+    if (embeddedDatabasePort != null) {
+      String correctUrl = buildH2JdbcUrl(embeddedDatabasePort);
+      warnIfUrlIsSet(embeddedDatabasePort, url, correctUrl);
+      props.set(JDBC_URL, correctUrl);
+      return Provider.H2;
+    }
+
+    if (isEmpty(url)) {
+      props.set(JDBC_URL, buildH2JdbcUrl(JDBC_EMBEDDED_PORT_DEFAULT_VALUE));
+      props.set(JDBC_EMBEDDED_PORT, String.valueOf(JDBC_EMBEDDED_PORT_DEFAULT_VALUE));
+      return Provider.H2;
+    }
+
+    Pattern pattern = Pattern.compile("jdbc:(\\w+):.+");
+    Matcher matcher = pattern.matcher(url);
+    if (!matcher.find()) {
+      throw new MessageException(format("Bad format of JDBC URL: %s", url));
+    }
+    String key = matcher.group(1);
+    try {
+      return Provider.valueOf(StringUtils.upperCase(key));
+    } catch (IllegalArgumentException e) {
+      throw new MessageException(format("Unsupported JDBC driver provider: %s", key));
+    }
+  }
+
+  private static String buildH2JdbcUrl(int embeddedDatabasePort) {
+    InetAddress ip = InetAddress.getLoopbackAddress();
+    String host;
+    if (ip instanceof Inet6Address) {
+      host = "[" + ip.getHostAddress() + "]";
+    } else {
+      host = ip.getHostAddress();
+    }
+    return format("jdbc:h2:tcp://%s:%d/sonar", host, embeddedDatabasePort);
+  }
+
+  void checkUrlParameters(Provider provider, String url) {
+    if (Provider.MYSQL.equals(provider)) {
+      checkRequiredParameter(url, "useUnicode=true");
+      checkRequiredParameter(url, "characterEncoding=utf8");
+      checkRecommendedParameter(url, "rewriteBatchedStatements=true");
+      checkRecommendedParameter(url, "useConfigs=maxPerformance");
+    }
+  }
+
+  private static void warnIfUrlIsSet(int port, String existing, String expectedUrl) {
+    if (isNotEmpty(existing)) {
+      Logger logger = LoggerFactory.getLogger(JdbcSettings.class);
+      if (expectedUrl.equals(existing)) {
+        logger.warn("To change H2 database port, only property '{}' should be set (which current value is '{}'). " +
+          "Remove property '{}' from configuration to remove this warning.",
+          JDBC_EMBEDDED_PORT, port,
+          JDBC_URL);
+      } else {
+        logger.warn("Both '{}' and '{}' properties are set. " +
+          "The value of property '{}' ('{}') is not consistent with the value of property '{}' ('{}'). " +
+          "The value of property '{}' will be ignored and value '{}' will be used instead. " +
+          "To remove this warning, either remove property '{}' if your intent was to use the embedded H2 database, otherwise remove property '{}'.",
+          JDBC_EMBEDDED_PORT, JDBC_URL,
+          JDBC_URL, existing, JDBC_EMBEDDED_PORT, port,
+          JDBC_URL, expectedUrl,
+          JDBC_URL, JDBC_EMBEDDED_PORT);
+      }
+    }
+  }
+
+  private static void checkRequiredParameter(String url, String val) {
+    if (!url.contains(val)) {
+      throw new MessageException(format("JDBC URL must have the property '%s'", val));
+    }
+  }
+
+  private void checkRecommendedParameter(String url, String val) {
+    if (!url.contains(val)) {
+      LoggerFactory.getLogger(getClass()).warn("JDBC URL is recommended to have the property '{}'", val);
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java b/server/sonar-main/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java
new file mode 100644 (file)
index 0000000..11b23e9
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.config;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+import static java.lang.String.format;
+
+public class SonarQubeVersionHelper {
+  private static final String SONARQUBE_VERSION_PATH = "/sonarqube-version.txt";
+
+  private static String sonarqubeVersion;
+
+  private SonarQubeVersionHelper() {
+    // only static methods
+  }
+
+  public static String getSonarqubeVersion() {
+    if (sonarqubeVersion == null) {
+      loadVersion();
+    }
+    return sonarqubeVersion;
+  }
+
+  private static synchronized void loadVersion() {
+    try {
+      try (BufferedReader in = new BufferedReader(
+        new InputStreamReader(
+          SonarQubeVersionHelper.class.getResourceAsStream(SONARQUBE_VERSION_PATH),
+          StandardCharsets.UTF_8
+        ))) {
+        sonarqubeVersion = in.readLine();
+      }
+    } catch (IOException e) {
+      throw new IllegalStateException(format("Cannot load %s from classpath", SONARQUBE_VERSION_PATH), e);
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/package-info.java b/server/sonar-main/src/main/java/org/sonar/application/config/package-info.java
new file mode 100644 (file)
index 0000000..192ba67
--- /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.config;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-main/src/main/java/org/sonar/application/package-info.java b/server/sonar-main/src/main/java/org/sonar/application/package-info.java
new file mode 100644 (file)
index 0000000..7dc3ab2
--- /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;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/AbstractProcessMonitor.java b/server/sonar-main/src/main/java/org/sonar/application/process/AbstractProcessMonitor.java
new file mode 100644 (file)
index 0000000..1583a72
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessId;
+
+abstract class AbstractProcessMonitor implements ProcessMonitor {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AbstractProcessMonitor.class);
+  private static final int EXPECTED_EXIT_VALUE = 0;
+
+  protected final Process process;
+  private final ProcessId processId;
+
+  protected AbstractProcessMonitor(Process process, ProcessId processId) {
+    this.process = process;
+    this.processId = processId;
+  }
+
+  public InputStream getInputStream() {
+    return process.getInputStream();
+  }
+
+  public InputStream getErrorStream() {
+    return process.getErrorStream();
+  }
+
+  public void closeStreams() {
+    closeQuietly(process.getInputStream());
+    closeQuietly(process.getOutputStream());
+    closeQuietly(process.getErrorStream());
+  }
+
+  private static void closeQuietly(@Nullable Closeable closeable) {
+    try {
+      if (closeable != null) {
+        closeable.close();
+      }
+    } catch (IOException ignored) {
+      // ignore
+    }
+  }
+
+  public boolean isAlive() {
+    return process.isAlive();
+  }
+
+  public void destroyForcibly() {
+    process.destroyForcibly();
+  }
+
+  public void waitFor() throws InterruptedException {
+    int exitValue = process.waitFor();
+    if (exitValue != EXPECTED_EXIT_VALUE) {
+      LOG.warn("Process exited with exit value [{}]: {}", processId.getKey(), exitValue);
+    } else {
+      LOG.debug("Process exited with exit value [{}]: {}", processId.getKey(), exitValue);
+    }
+  }
+
+  public void waitFor(long timeout, TimeUnit unit) throws InterruptedException {
+    process.waitFor(timeout, unit);
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java b/server/sonar-main/src/main/java/org/sonar/application/process/EsProcessMonitor.java
new file mode 100644 (file)
index 0000000..52c0a9d
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import com.google.common.net.HostAndPort;
+import io.netty.util.ThreadDeathWatcher;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.elasticsearch.client.transport.NoNodeAvailableException;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.common.network.NetworkModule;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.elasticsearch.common.transport.TransportAddress;
+import org.elasticsearch.transport.Netty4Plugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessId;
+import org.sonar.process.command.EsCommand;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.unmodifiableList;
+import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
+import static org.sonar.application.process.EsProcessMonitor.Status.CONNECTION_REFUSED;
+import static org.sonar.application.process.EsProcessMonitor.Status.GREEN;
+import static org.sonar.application.process.EsProcessMonitor.Status.KO;
+import static org.sonar.application.process.EsProcessMonitor.Status.RED;
+import static org.sonar.application.process.EsProcessMonitor.Status.YELLOW;
+
+public class EsProcessMonitor extends AbstractProcessMonitor {
+  private static final Logger LOG = LoggerFactory.getLogger(EsProcessMonitor.class);
+  private static final int WAIT_FOR_UP_DELAY_IN_MILLIS = 100;
+  private static final int WAIT_FOR_UP_TIMEOUT = 10 * 60; /* 1min */
+
+  private final AtomicBoolean nodeUp = new AtomicBoolean(false);
+  private final AtomicBoolean nodeOperational = new AtomicBoolean(false);
+  private final EsCommand esCommand;
+  private AtomicReference<TransportClient> transportClient = new AtomicReference<>(null);
+
+  public EsProcessMonitor(Process process, ProcessId processId, EsCommand esCommand) {
+    super(process, processId);
+    this.esCommand = esCommand;
+  }
+
+  @Override
+  public boolean isOperational() {
+    if (nodeOperational.get()) {
+      return true;
+    }
+
+    boolean flag = false;
+    try {
+      flag = checkOperational();
+    } catch (InterruptedException e) {
+      LOG.trace("Interrupted while checking ES node is operational", e);
+      Thread.currentThread().interrupt();
+    } finally {
+      if (flag) {
+        transportClient.set(null);
+        nodeOperational.set(true);
+      }
+    }
+    return nodeOperational.get();
+  }
+
+  private boolean checkOperational() throws InterruptedException {
+    int i = 0;
+    Status status = checkStatus();
+    do {
+      if (status != Status.CONNECTION_REFUSED) {
+        nodeUp.set(true);
+      } else {
+        Thread.sleep(WAIT_FOR_UP_DELAY_IN_MILLIS);
+        i++;
+        status = checkStatus();
+      }
+    } while (!nodeUp.get() && i < WAIT_FOR_UP_TIMEOUT);
+    return status == YELLOW || status == GREEN;
+  }
+
+  static class MinimalTransportClient extends TransportClient {
+
+    MinimalTransportClient(Settings settings) {
+      super(settings, unmodifiableList(singletonList(Netty4Plugin.class)));
+    }
+
+    @Override
+    public void close() {
+      super.close();
+      if (NetworkModule.TRANSPORT_TYPE_SETTING.exists(settings) == false
+          || NetworkModule.TRANSPORT_TYPE_SETTING.get(settings).equals(Netty4Plugin.NETTY_TRANSPORT_NAME)) {
+        try {
+          GlobalEventExecutor.INSTANCE.awaitInactivity(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+        try {
+          ThreadDeathWatcher.awaitInactivity(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+      }
+    }
+
+  }
+
+  private Status checkStatus() {
+    try {
+      ClusterHealthResponse response = getTransportClient().admin().cluster()
+        .health(new ClusterHealthRequest().waitForStatus(ClusterHealthStatus.YELLOW).timeout(timeValueSeconds(30)))
+        .actionGet();
+      if (response.getStatus() == ClusterHealthStatus.GREEN) {
+        return GREEN;
+      }
+      if (response.getStatus() == ClusterHealthStatus.YELLOW) {
+        return YELLOW;
+      }
+      if (response.getStatus() == ClusterHealthStatus.RED) {
+        return RED;
+      }
+      return KO;
+    } catch (NoNodeAvailableException e) {
+      return CONNECTION_REFUSED;
+    } catch (Exception e) {
+      LOG.error("Failed to check status", e);
+      return KO;
+    }
+  }
+
+  private TransportClient getTransportClient() {
+    TransportClient res = this.transportClient.get();
+    if (res == null) {
+      res = buildTransportClient();
+      if (this.transportClient.compareAndSet(null, res)) {
+        return res;
+      }
+      return this.transportClient.get();
+    }
+    return res;
+  }
+
+  private TransportClient buildTransportClient() {
+    Settings.Builder esSettings = Settings.builder();
+
+    // mandatory property defined by bootstrap process
+    esSettings.put("cluster.name", esCommand.getClusterName());
+
+    TransportClient nativeClient = new MinimalTransportClient(esSettings.build());
+    HostAndPort host = HostAndPort.fromParts(esCommand.getHost(), esCommand.getPort());
+    addHostToClient(host, nativeClient);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Connected to Elasticsearch node: [{}]", displayedAddresses(nativeClient));
+    }
+    return nativeClient;
+  }
+
+  private static void addHostToClient(HostAndPort host, TransportClient client) {
+    try {
+      client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host.getHostText()), host.getPortOrDefault(9001)));
+    } catch (UnknownHostException e) {
+      throw new IllegalStateException("Can not resolve host [" + host + "]", e);
+    }
+  }
+
+  private static String displayedAddresses(TransportClient nativeClient) {
+    return nativeClient.transportAddresses().stream().map(TransportAddress::toString).collect(Collectors.joining(", "));
+  }
+
+  enum Status {
+    CONNECTION_REFUSED, KO, RED, YELLOW, GREEN
+  }
+
+  @Override
+  public void askForStop() {
+    process.destroy();
+  }
+
+  @Override
+  public boolean askedForRestart() {
+    // ES does not support asking for restart
+    return false;
+  }
+
+  @Override
+  public void acknowledgeAskForRestart() {
+    // nothing to do
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/Lifecycle.java b/server/sonar-main/src/main/java/org/sonar/application/process/Lifecycle.java
new file mode 100644 (file)
index 0000000..4d185fa
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessId;
+
+import static org.sonar.application.process.Lifecycle.State.INIT;
+import static org.sonar.application.process.Lifecycle.State.STARTED;
+import static org.sonar.application.process.Lifecycle.State.STARTING;
+import static org.sonar.application.process.Lifecycle.State.STOPPED;
+import static org.sonar.application.process.Lifecycle.State.STOPPING;
+
+public class Lifecycle {
+
+  public enum State {
+    INIT, STARTING, STARTED, STOPPING, STOPPED
+  }
+
+  private static final Logger LOG = LoggerFactory.getLogger(Lifecycle.class);
+  private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
+
+  private final ProcessId processId;
+  private final List<ProcessLifecycleListener> listeners;
+  private State state;
+
+  public Lifecycle(ProcessId processId, List<ProcessLifecycleListener> listeners) {
+    this(processId, listeners, INIT);
+  }
+
+  Lifecycle(ProcessId processId, List<ProcessLifecycleListener> listeners, State initialState) {
+    this.processId = processId;
+    this.listeners = listeners;
+    this.state = initialState;
+  }
+
+  private static Map<State, Set<State>> buildTransitions() {
+    Map<State, Set<State>> res = new EnumMap<>(State.class);
+    res.put(INIT, toSet(STARTING));
+    res.put(STARTING, toSet(STARTED, STOPPING, STOPPED));
+    res.put(STARTED, toSet(STOPPING, STOPPED));
+    res.put(STOPPING, toSet(STOPPED));
+    res.put(STOPPED, toSet());
+    return res;
+  }
+
+  private static Set<State> toSet(State... states) {
+    if (states.length == 0) {
+      return Collections.emptySet();
+    }
+    if (states.length == 1) {
+      return Collections.singleton(states[0]);
+    }
+    return EnumSet.copyOf(Arrays.asList(states));
+  }
+
+  State getState() {
+    return state;
+  }
+
+  synchronized boolean tryToMoveTo(State to) {
+    boolean res = false;
+    State currentState = state;
+    if (TRANSITIONS.get(currentState).contains(to)) {
+      this.state = to;
+      res = true;
+      listeners.forEach(listener -> listener.onProcessState(processId, to));
+    }
+    LOG.trace("tryToMoveTo {} from {} to {} => {}", processId.getKey(), currentState, to, res);
+    return res;
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java
new file mode 100644 (file)
index 0000000..a54f53f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import org.sonar.process.ProcessId;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
+
+import static java.util.Objects.requireNonNull;
+
+class ProcessCommandsProcessMonitor extends AbstractProcessMonitor {
+
+  private final ProcessCommands commands;
+
+  ProcessCommandsProcessMonitor(Process process, ProcessId processId, ProcessCommands commands) {
+    super(process, processId);
+    this.commands = requireNonNull(commands, "commands can't be null");
+  }
+
+  /**
+   * Whether the process has set the operational flag (in ipc shared memory)
+   */
+  @Override
+  public boolean isOperational() {
+    return commands.isOperational();
+  }
+
+  /**
+   * Send request to gracefully stop to the process (via ipc shared memory)
+   */
+  @Override
+  public void askForStop() {
+    commands.askForStop();
+  }
+
+  /**
+   * Whether the process asked for a full restart (via ipc shared memory)
+   */
+  @Override
+  public boolean askedForRestart() {
+    return commands.askedForRestart();
+  }
+
+  /**
+   * Removes the flag in ipc shared memory so that next call to {@link #askedForRestart()}
+   * returns {@code false}, except if meanwhile process asks again for restart.
+   */
+  @Override
+  public void acknowledgeAskForRestart() {
+    commands.acknowledgeAskForRestart();
+  }
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessEventListener.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessEventListener.java
new file mode 100644 (file)
index 0000000..a7f6a7b
--- /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.process;
+
+import org.sonar.process.ProcessId;
+
+@FunctionalInterface
+public interface ProcessEventListener {
+
+  enum Type {
+    OPERATIONAL,
+    ASK_FOR_RESTART
+  }
+
+  /**
+   * This method is called when the process with the specified {@link ProcessId}
+   * sends the event through the ipc shared memory.
+   * Note that there can be a delay since the instant the process sets the flag
+   * (see {@link SQProcess#WATCHER_DELAY_MS}).
+   *
+   * Call blocks the process watcher. Implementations should be asynchronous and
+   * fork a new thread if call can be long.
+   */
+  void onProcessEvent(ProcessId processId, Type type);
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncher.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncher.java
new file mode 100644 (file)
index 0000000..c39f91b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.Closeable;
+import org.sonar.process.command.EsCommand;
+import org.sonar.process.command.JavaCommand;
+
+public interface ProcessLauncher extends Closeable {
+
+  @Override
+  void close();
+
+  /**
+   * Launch an ES command.
+   *
+   * @throws IllegalStateException if an error occurs
+   */
+  ProcessMonitor launch(EsCommand esCommand);
+
+  /**
+   * Launch a Java command.
+   * 
+   * @throws IllegalStateException if an error occurs
+   */
+  ProcessMonitor launch(JavaCommand javaCommand);
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
new file mode 100644 (file)
index 0000000..549e4dd
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+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 org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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;
+
+import static java.lang.String.format;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;
+
+public class ProcessLauncherImpl implements ProcessLauncher {
+  private static final Logger LOG = LoggerFactory.getLogger(ProcessLauncherImpl.class);
+
+  private final File tempDir;
+  private final AllProcessesCommands allProcessesCommands;
+  private final Supplier<ProcessBuilder> processBuilderSupplier;
+
+  public ProcessLauncherImpl(File tempDir) {
+    this(tempDir, new AllProcessesCommands(tempDir), JavaLangProcessBuilder::new);
+  }
+
+  ProcessLauncherImpl(File tempDir, AllProcessesCommands allProcessesCommands, Supplier<ProcessBuilder> processBuilderSupplier) {
+    this.tempDir = tempDir;
+    this.allProcessesCommands = allProcessesCommands;
+    this.processBuilderSupplier = processBuilderSupplier;
+  }
+
+  @Override
+  public void close() {
+    allProcessesCommands.close();
+  }
+
+  @Override
+  public ProcessMonitor launch(EsCommand esCommand) {
+    Process process = null;
+    ProcessId processId = esCommand.getProcessId();
+    try {
+      writeConfFiles(esCommand);
+      ProcessBuilder processBuilder = create(esCommand);
+      logLaunchedCommand(esCommand, processBuilder);
+
+      process = processBuilder.start();
+
+      return new EsProcessMonitor(process, processId, esCommand);
+    } catch (Exception e) {
+      // just in case
+      if (process != null) {
+        process.destroyForcibly();
+      }
+      throw new IllegalStateException(format("Fail to launch process [%s]", processId.getKey()), e);
+    }
+  }
+
+  private void writeConfFiles(EsCommand esCommand) {
+    File confDir = esCommand.getConfDir();
+    if (!confDir.exists() && !confDir.mkdirs()) {
+      String error = format("Failed to create temporary configuration directory [%s]", confDir.getAbsolutePath());
+      LOG.error(error);
+      throw new IllegalStateException(error);
+    }
+
+    try {
+      IOUtils.copy(getClass().getResourceAsStream("elasticsearch.yml"), new FileOutputStream(new File(confDir, "elasticsearch.yml")));
+      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);
+    }
+  }
+
+  @Override
+  public ProcessMonitor launch(JavaCommand javaCommand) {
+    Process process = null;
+    ProcessId processId = javaCommand.getProcessId();
+    try {
+      ProcessCommands commands = allProcessesCommands.createAfterClean(processId.getIpcIndex());
+
+      ProcessBuilder processBuilder = create(javaCommand);
+      logLaunchedCommand(javaCommand, processBuilder);
+      process = processBuilder.start();
+      return new ProcessCommandsProcessMonitor(process, processId, commands);
+    } catch (Exception e) {
+      // just in case
+      if (process != null) {
+        process.destroyForcibly();
+      }
+      throw new IllegalStateException(format("Fail to launch process [%s]", processId.getKey()), e);
+    }
+  }
+
+  private static <T extends AbstractCommand> void logLaunchedCommand(AbstractCommand<T> command, ProcessBuilder processBuilder) {
+    if (LOG.isInfoEnabled()) {
+      LOG.info("Launch process[{}] from [{}]: {}",
+        command.getProcessId(),
+        command.getWorkDir().getAbsolutePath(),
+        String.join(" ", processBuilder.command()));
+    }
+  }
+
+  private ProcessBuilder create(EsCommand esCommand) {
+    List<String> commands = new ArrayList<>();
+    commands.add(esCommand.getExecutable().getAbsolutePath());
+    commands.addAll(esCommand.getEsOptions());
+
+    return create(esCommand, commands);
+  }
+
+  private <T extends JvmOptions> ProcessBuilder create(JavaCommand<T> javaCommand) {
+    List<String> commands = new ArrayList<>();
+    commands.add(buildJavaPath());
+    commands.addAll(javaCommand.getJvmOptions().getAll());
+    commands.addAll(buildClasspath(javaCommand));
+    commands.add(javaCommand.getClassName());
+    commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
+
+    return create(javaCommand, commands);
+  }
+
+  private ProcessBuilder create(AbstractCommand<?> javaCommand, List<String> commands) {
+    ProcessBuilder processBuilder = processBuilderSupplier.get();
+    processBuilder.command(commands);
+    processBuilder.directory(javaCommand.getWorkDir());
+    processBuilder.environment().putAll(javaCommand.getEnvVariables());
+    processBuilder.redirectErrorStream(true);
+    return processBuilder;
+  }
+
+  private static String buildJavaPath() {
+    String separator = System.getProperty("file.separator");
+    return new File(new File(System.getProperty("java.home")), "bin" + separator + "java").getAbsolutePath();
+  }
+
+  private static List<String> buildClasspath(JavaCommand javaCommand) {
+    String pathSeparator = System.getProperty("path.separator");
+    return Arrays.asList("-cp", String.join(pathSeparator, javaCommand.getClasspath()));
+  }
+
+  private File buildPropertiesFile(JavaCommand javaCommand) {
+    File propertiesFile = null;
+    try {
+      propertiesFile = File.createTempFile("sq-process", "properties", tempDir);
+      Properties props = new Properties();
+      props.putAll(javaCommand.getArguments());
+      props.setProperty(PROPERTY_PROCESS_KEY, javaCommand.getProcessId().getKey());
+      props.setProperty(PROPERTY_PROCESS_INDEX, Integer.toString(javaCommand.getProcessId().getIpcIndex()));
+      props.setProperty(PROPERTY_TERMINATION_TIMEOUT, "60000");
+      props.setProperty(PROPERTY_SHARED_PATH, tempDir.getAbsolutePath());
+      try (OutputStream out = new FileOutputStream(propertiesFile)) {
+        props.store(out, format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
+      }
+      return propertiesFile;
+    } catch (Exception e) {
+      throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e);
+    }
+  }
+
+  /**
+   * An interface of the methods of {@link java.lang.ProcessBuilder} that we use in {@link ProcessLauncherImpl}.
+   * <p>Allows testing creating processes without actualling creating them at OS level</p>
+   */
+  public interface ProcessBuilder {
+    List<String> command();
+
+    ProcessBuilder command(List<String> commands);
+
+    ProcessBuilder directory(File dir);
+
+    Map<String, String> environment();
+
+    ProcessBuilder redirectErrorStream(boolean b);
+
+    Process start() throws IOException;
+  }
+
+  private static class JavaLangProcessBuilder implements ProcessBuilder {
+    private final java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder();
+
+    /**
+     * @see java.lang.ProcessBuilder#command()
+     */
+    @Override
+    public List<String> command() {
+      return builder.command();
+    }
+
+    /**
+     * @see java.lang.ProcessBuilder#command(List)
+     */
+    @Override
+    public ProcessBuilder command(List<String> commands) {
+      builder.command(commands);
+      return this;
+    }
+
+    /**
+     * @see java.lang.ProcessBuilder#directory(File)
+     */
+    @Override
+    public ProcessBuilder directory(File dir) {
+      builder.directory(dir);
+      return this;
+    }
+
+    /**
+     * @see java.lang.ProcessBuilder#environment()
+     */
+    @Override
+    public Map<String, String> environment() {
+      return builder.environment();
+    }
+
+    /**
+     * @see java.lang.ProcessBuilder#redirectErrorStream(boolean)
+     */
+    @Override
+    public ProcessBuilder redirectErrorStream(boolean b) {
+      builder.redirectErrorStream(b);
+      return this;
+    }
+
+    /**
+     * @see java.lang.ProcessBuilder#start()
+     */
+    @Override
+    public Process start() throws IOException {
+      return builder.start();
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java
new file mode 100644 (file)
index 0000000..384499b
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import org.sonar.process.ProcessId;
+
+@FunctionalInterface
+public interface ProcessLifecycleListener {
+
+  /**
+   * This method is called when the state of the process with the specified {@link ProcessId}
+   * changes.
+   *
+   * Call blocks the process watcher. Implementations should be asynchronous and
+   * fork a new thread if call can be long.
+   */
+  void onProcessState(ProcessId processId, Lifecycle.State state);
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/ProcessMonitor.java b/server/sonar-main/src/main/java/org/sonar/application/process/ProcessMonitor.java
new file mode 100644 (file)
index 0000000..9a88393
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+
+public interface ProcessMonitor {
+
+  /**
+   * @see Process#getInputStream()
+   */
+  InputStream getInputStream();
+
+  /**
+   * @see Process#getErrorStream()
+   */
+  InputStream getErrorStream();
+
+  /**
+   * Closes the streams {@link Process#getInputStream()}, {@link Process#getOutputStream()}
+   * and {@link Process#getErrorStream()}.
+   *
+   * No exceptions are thrown in case of errors.
+   */
+  void closeStreams();
+
+  /**
+   * @see Process#isAlive()
+   */
+  boolean isAlive();
+
+  /**
+   * @see Process#destroyForcibly()
+   */
+  void destroyForcibly();
+
+  /**
+   * @see Process#waitFor()
+   */
+  void waitFor() throws InterruptedException;
+
+  /**
+   * @see Process#waitFor(long, TimeUnit)
+   */
+  void waitFor(long timeout, TimeUnit timeoutUnit) throws InterruptedException;
+
+  /**
+   * Whether the process has reach operational state after startup.
+   */
+  boolean isOperational();
+
+  /**
+   * Send request to gracefully stop to the process
+   */
+  void askForStop();
+
+  /**
+   * Whether the process asked for a full restart
+   */
+  boolean askedForRestart();
+
+  /**
+   * Sends a signal to the process to acknowledge that the parent process received the request to restart from the
+   * child process send via {@link #askedForRestart()}.
+   * <br/>
+   * Child process will typically stop sending the signal requesting restart from now on.
+   */
+  void acknowledgeAskForRestart();
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/SQProcess.java b/server/sonar-main/src/main/java/org/sonar/application/process/SQProcess.java
new file mode 100644 (file)
index 0000000..6aa7a29
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.application.process;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.ProcessId;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+public class SQProcess {
+
+  public static final long DEFAULT_WATCHER_DELAY_MS = 500L;
+  private static final Logger LOG = LoggerFactory.getLogger(SQProcess.class);
+
+  private final ProcessId processId;
+  private final Lifecycle lifecycle;
+  private final List<ProcessEventListener> eventListeners;
+  private final long watcherDelayMs;
+
+  private ProcessMonitor process;
+  private StreamGobbler stdOutGobbler;
+  private StreamGobbler stdErrGobbler;
+  private final StopWatcher stopWatcher;
+  private final EventWatcher eventWatcher;
+  // keep flag so that the operational event is sent only once
+  // to listeners
+  private final AtomicBoolean operational = new AtomicBoolean(false);
+
+  private SQProcess(Builder builder) {
+    this.processId = requireNonNull(builder.processId, "processId can't be null");
+    this.lifecycle = new Lifecycle(this.processId, builder.lifecycleListeners);
+    this.eventListeners = builder.eventListeners;
+    this.watcherDelayMs = builder.watcherDelayMs;
+    this.stopWatcher = new StopWatcher();
+    this.eventWatcher = new EventWatcher();
+  }
+
+  public boolean start(Supplier<ProcessMonitor> commandLauncher) {
+    if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
+      // has already been started
+      return false;
+    }
+    try {
+      this.process = commandLauncher.get();
+    } catch (RuntimeException e) {
+      LOG.error(format("Fail to launch process [%s]", processId.getKey()), e);
+      lifecycle.tryToMoveTo(Lifecycle.State.STOPPED);
+      throw e;
+    }
+    this.stdOutGobbler = new StreamGobbler(process.getInputStream(), processId.getKey());
+    this.stdOutGobbler.start();
+    this.stdErrGobbler = new StreamGobbler(process.getErrorStream(), processId.getKey());
+    this.stdErrGobbler.start();
+    this.stopWatcher.start();
+    this.eventWatcher.start();
+    // Could be improved by checking the status "up" in shared memory.
+    // Not a problem so far as this state is not used by listeners.
+    lifecycle.tryToMoveTo(Lifecycle.State.STARTED);
+    return true;
+  }
+
+  public ProcessId getProcessId() {
+    return processId;
+  }
+
+  Lifecycle.State getState() {
+    return lifecycle.getState();
+  }
+
+  /**
+   * Sends kill signal and awaits termination. No guarantee that process is gracefully terminated (=shutdown hooks
+   * executed). It depends on OS.
+   */
+  public void stop(long timeout, TimeUnit timeoutUnit) {
+    if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) {
+      stopGracefully(timeout, timeoutUnit);
+      if (process != null && process.isAlive()) {
+        LOG.info("{} failed to stop in a timely fashion. Killing it.", processId.getKey());
+      }
+      // enforce stop and clean-up even if process has been gracefully stopped
+      stopForcibly();
+    } else {
+      // already stopping or stopped
+      waitForDown();
+    }
+  }
+
+  private void waitForDown() {
+    while (process != null && process.isAlive()) {
+      try {
+        process.waitFor();
+      } catch (InterruptedException ignored) {
+        // ignore, waiting for process to stop
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+
+  private void stopGracefully(long timeout, TimeUnit timeoutUnit) {
+    if (process == null) {
+      return;
+    }
+    try {
+      // request graceful stop
+      process.askForStop();
+      process.waitFor(timeout, timeoutUnit);
+    } catch (InterruptedException e) {
+      // can't wait for the termination of process. Let's assume it's down.
+      LOG.warn(format("Interrupted while stopping process %s", processId), e);
+      Thread.currentThread().interrupt();
+    } catch (Throwable e) {
+      LOG.error("Can not ask for graceful stop of process " + processId, e);
+    }
+  }
+
+  public void stopForcibly() {
+    eventWatcher.interrupt();
+    stopWatcher.interrupt();
+    if (process != null) {
+      process.destroyForcibly();
+      waitForDown();
+      process.closeStreams();
+    }
+    if (stdOutGobbler != null) {
+      StreamGobbler.waitUntilFinish(stdOutGobbler);
+      stdOutGobbler.interrupt();
+    }
+    if (stdErrGobbler != null) {
+      StreamGobbler.waitUntilFinish(stdErrGobbler);
+      stdErrGobbler.interrupt();
+    }
+    lifecycle.tryToMoveTo(Lifecycle.State.STOPPED);
+  }
+
+  void refreshState() {
+    if (process.isAlive()) {
+      if (!operational.get() && process.isOperational()) {
+        operational.set(true);
+        eventListeners.forEach(l -> l.onProcessEvent(processId, ProcessEventListener.Type.OPERATIONAL));
+      }
+      if (process.askedForRestart()) {
+        process.acknowledgeAskForRestart();
+        eventListeners.forEach(l -> l.onProcessEvent(processId, ProcessEventListener.Type.ASK_FOR_RESTART));
+      }
+    } else {
+      stopForcibly();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return format("Process[%s]", processId.getKey());
+  }
+
+  /**
+   * This thread blocks as long as the monitored process is physically alive.
+   * It avoids from executing {@link Process#exitValue()} at a fixed rate :
+   * <ul>
+   *   <li>no usage of exception for flow control. Indeed {@link Process#exitValue()} throws an exception
+   *   if process is alive. There's no method <code>Process#isAlive()</code></li>
+   *   <li>no delay, instantaneous notification that process is down</li>
+   * </ul>
+   */
+  private class StopWatcher extends Thread {
+    StopWatcher() {
+      // this name is different than Thread#toString(), which includes name, priority
+      // and thread group
+      // -> do not override toString()
+      super(format("StopWatcher[%s]", processId.getKey()));
+    }
+
+    @Override
+    public void run() {
+      try {
+        process.waitFor();
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        // stop watching process
+      }
+      stopForcibly();
+    }
+  }
+
+  private class EventWatcher extends Thread {
+    EventWatcher() {
+      // this name is different than Thread#toString(), which includes name, priority
+      // and thread group
+      // -> do not override toString()
+      super(format("EventWatcher[%s]", processId.getKey()));
+    }
+
+    @Override
+    public void run() {
+      try {
+        while (process.isAlive()) {
+          refreshState();
+          Thread.sleep(watcherDelayMs);
+        }
+      } catch (InterruptedException e) {
+        // request to stop watching process. To avoid unexpected behaviors
+        // the process is stopped.
+        Thread.currentThread().interrupt();
+        stopForcibly();
+      }
+    }
+  }
+
+  public static Builder builder(ProcessId processId) {
+    return new Builder(processId);
+  }
+
+  public static class Builder {
+    private final ProcessId processId;
+    private final List<ProcessEventListener> eventListeners = new ArrayList<>();
+    private final List<ProcessLifecycleListener> lifecycleListeners = new ArrayList<>();
+    private long watcherDelayMs = DEFAULT_WATCHER_DELAY_MS;
+
+    private Builder(ProcessId processId) {
+      this.processId = processId;
+    }
+
+    public Builder addEventListener(ProcessEventListener listener) {
+      this.eventListeners.add(listener);
+      return this;
+    }
+
+    public Builder addProcessLifecycleListener(ProcessLifecycleListener listener) {
+      this.lifecycleListeners.add(listener);
+      return this;
+    }
+
+    /**
+     * Default delay is {@link #DEFAULT_WATCHER_DELAY_MS}
+     */
+    public Builder setWatcherDelayMs(long l) {
+      this.watcherDelayMs = l;
+      return this;
+    }
+
+    public SQProcess build() {
+      return new SQProcess(this);
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/StopRequestWatcher.java b/server/sonar-main/src/main/java/org/sonar/application/process/StopRequestWatcher.java
new file mode 100644 (file)
index 0000000..2fef4ab
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+/**
+ * Background thread that checks if a stop request
+ * is sent, usually by Orchestrator
+ */
+public interface StopRequestWatcher {
+
+  void startWatching();
+
+  void stopWatching();
+
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java b/server/sonar-main/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
new file mode 100644 (file)
index 0000000..fc042f6
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import org.sonar.application.FileSystem;
+import org.sonar.application.Scheduler;
+import org.sonar.application.config.AppSettings;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+
+public class StopRequestWatcherImpl extends Thread implements StopRequestWatcher {
+
+  private static final long DEFAULT_WATCHER_DELAY_MS = 500L;
+
+  private final ProcessCommands commands;
+  private final Scheduler scheduler;
+  private final AppSettings settings;
+  private long delayMs = DEFAULT_WATCHER_DELAY_MS;
+
+  StopRequestWatcherImpl(AppSettings settings, Scheduler scheduler, ProcessCommands commands) {
+    super("StopRequestWatcherImpl");
+    this.settings = settings;
+    this.commands = commands;
+    this.scheduler = scheduler;
+
+    // safeguard, do not block the JVM if thread is not interrupted
+    // (method stopWatching() never called).
+    setDaemon(true);
+  }
+
+  public static StopRequestWatcherImpl create(AppSettings settings, Scheduler scheduler, FileSystem fs) {
+    DefaultProcessCommands commands = DefaultProcessCommands.secondary(fs.getTempDir(), ProcessId.APP.getIpcIndex());
+    return new StopRequestWatcherImpl(settings, scheduler, commands);
+  }
+
+  long getDelayMs() {
+    return delayMs;
+  }
+
+  void setDelayMs(long delayMs) {
+    this.delayMs = delayMs;
+  }
+
+  @Override
+  public void run() {
+    try {
+      while (true) {
+        if (commands.askedForStop()) {
+          scheduler.terminate();
+          return;
+        }
+        Thread.sleep(delayMs);
+      }
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      // stop watching the commands
+    }
+  }
+
+  @Override
+  public void startWatching() {
+    if (settings.getProps().valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND)) {
+      start();
+    }
+  }
+
+  @Override
+  public void stopWatching() {
+    // does nothing is not started
+    interrupt();
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/StreamGobbler.java b/server/sonar-main/src/main/java/org/sonar/application/process/StreamGobbler.java
new file mode 100644 (file)
index 0000000..b920360
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import javax.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Reads process output and writes to logs
+ */
+public class StreamGobbler extends Thread {
+
+  public static final String LOGGER_GOBBLER = "gobbler";
+
+  private final InputStream is;
+  private final Logger logger;
+
+  StreamGobbler(InputStream is, String processKey) {
+    this(is, processKey, LoggerFactory.getLogger(LOGGER_GOBBLER));
+  }
+
+  StreamGobbler(InputStream is, String processKey, Logger logger) {
+    super(String.format("Gobbler[%s]", processKey));
+    this.is = is;
+    this.logger = logger;
+  }
+
+  @Override
+  public void run() {
+    try (BufferedReader br = new BufferedReader(new InputStreamReader(is, UTF_8))) {
+      String line;
+      while ((line = br.readLine()) != null) {
+        logger.info(line);
+      }
+    } catch (Exception ignored) {
+      // ignored
+    }
+  }
+
+  static void waitUntilFinish(@Nullable StreamGobbler gobbler) {
+    if (gobbler != null) {
+      try {
+        gobbler.join();
+      } catch (InterruptedException ignored) {
+        // consider as finished, restore the interrupted flag
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+}
diff --git a/server/sonar-main/src/main/java/org/sonar/application/process/package-info.java b/server/sonar-main/src/main/java/org/sonar/application/process/package-info.java
new file mode 100644 (file)
index 0000000..26e8d8e
--- /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.process;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-main/src/main/resources/org/sonar/application/process/elasticsearch.yml b/server/sonar-main/src/main/resources/org/sonar/application/process/elasticsearch.yml
new file mode 100644 (file)
index 0000000..bca8635
--- /dev/null
@@ -0,0 +1,91 @@
+# ======================== Elasticsearch Configuration =========================
+#
+# NOTE: Elasticsearch comes with reasonable defaults for most settings.
+#       Before you set out to tweak and tune the configuration, make sure you
+#       understand what are you trying to accomplish and the consequences.
+#
+# The primary way of configuring a node is via this file. This template lists
+# the most important settings you may want to configure for a production cluster.
+#
+# Please see the documentation for further information on configuration options:
+# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/settings.html>
+#
+# ---------------------------------- Cluster -----------------------------------
+#
+# Use a descriptive name for your cluster:
+#
+#cluster.name: my-application
+#
+# ------------------------------------ Node ------------------------------------
+#
+# Use a descriptive name for the node:
+#
+#node.name: node-1
+#
+# Add custom attributes to the node:
+#
+#node.attr.rack: r1
+#
+# ----------------------------------- Paths ------------------------------------
+#
+# Path to directory where to store the data (separate multiple locations by comma):
+#
+#path.data: /path/to/data
+#
+# Path to log files:
+#
+#path.logs: /path/to/logs
+#
+# ----------------------------------- Memory -----------------------------------
+#
+# Lock the memory on startup:
+#
+#bootstrap.memory_lock: true
+#
+# Make sure that the heap size is set to about half the memory available
+# on the system and that the owner of the process is allowed to use this
+# limit.
+#
+# Elasticsearch performs poorly when the system is swapping the memory.
+#
+# ---------------------------------- Network -----------------------------------
+#
+# Set the bind address to a specific IP (IPv4 or IPv6):
+#
+#network.host: 192.168.0.1
+#
+# Set a custom port for HTTP:
+#
+#http.port: 9200
+#
+# For more information, see the documentation at:
+# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-network.html>
+#
+# --------------------------------- Discovery ----------------------------------
+#
+# Pass an initial list of hosts to perform discovery when new node is started:
+# The default list of hosts is ["127.0.0.1", "[::1]"]
+#
+#discovery.zen.ping.unicast.hosts: ["host1", "host2"]
+#
+# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1):
+#
+#discovery.zen.minimum_master_nodes: 3
+#
+# For more information, see the documentation at:
+# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-discovery-zen.html>
+#
+# ---------------------------------- Gateway -----------------------------------
+#
+# Block initial recovery after a full cluster restart until N nodes are started:
+#
+#gateway.recover_after_nodes: 3
+#
+# For more information, see the documentation at:
+# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-gateway.html>
+#
+# ---------------------------------- Various -----------------------------------
+#
+# Require explicit names when deleting indices:
+#
+#action.destructive_requires_name: true
diff --git a/server/sonar-main/src/main/resources/sonarqube-version.txt b/server/sonar-main/src/main/resources/sonarqube-version.txt
new file mode 100644 (file)
index 0000000..6b7ce46
--- /dev/null
@@ -0,0 +1 @@
+${buildVersion}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppFileSystemTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppFileSystemTest.java
new file mode 100644 (file)
index 0000000..3786190
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import javax.annotation.CheckForNull;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.sharedmemoryfile.ProcessCommands.MAX_PROCESSES;
+
+public class AppFileSystemTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private File homeDir;
+  private File dataDir;
+  private File tempDir;
+  private File logsDir;
+  private File webDir;
+  private TestAppSettings settings = new TestAppSettings();
+  private AppFileSystem underTest = new AppFileSystem(settings);
+
+  @Before
+  public void before() throws IOException {
+    homeDir = temp.newFolder();
+    dataDir = new File(homeDir, "data");
+    tempDir = new File(homeDir, "temp");
+    logsDir = new File(homeDir, "logs");
+    webDir = new File(homeDir, "web");
+
+    settings.getProps().set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
+    settings.getProps().set(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
+    settings.getProps().set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
+    settings.getProps().set(ProcessProperties.PATH_LOGS, logsDir.getAbsolutePath());
+    settings.getProps().set(ProcessProperties.PATH_WEB, webDir.getAbsolutePath());
+  }
+
+  @Test
+  public void reset_creates_dirs_if_they_don_t_exist() throws Exception {
+    assertThat(dataDir).doesNotExist();
+
+    underTest.reset();
+
+    assertThat(dataDir).exists().isDirectory();
+    assertThat(logsDir).exists().isDirectory();
+    assertThat(tempDir).exists().isDirectory();
+    assertThat(webDir).exists().isDirectory();
+
+    underTest.reset();
+
+    assertThat(dataDir).exists().isDirectory();
+    assertThat(logsDir).exists().isDirectory();
+    assertThat(tempDir).exists().isDirectory();
+    assertThat(webDir).exists().isDirectory();
+  }
+
+  @Test
+  public void reset_deletes_content_of_temp_dir_but_not_temp_dir_itself_if_it_already_exists() throws Exception {
+    assertThat(tempDir.mkdir()).isTrue();
+    Object tempDirKey = getFileKey(tempDir);
+    File fileInTempDir = new File(tempDir, "someFile.txt");
+    assertThat(fileInTempDir.createNewFile()).isTrue();
+    File subDirInTempDir = new File(tempDir, "subDir");
+    assertThat(subDirInTempDir.mkdir()).isTrue();
+
+    underTest.reset();
+
+    assertThat(tempDir).exists();
+    assertThat(fileInTempDir).doesNotExist();
+    assertThat(subDirInTempDir).doesNotExist();
+    assertThat(getFileKey(tempDir)).isEqualTo(tempDirKey);
+  }
+
+  @Test
+  public void reset_deletes_content_of_temp_dir_but_not_sharedmemory_file() throws Exception {
+    assertThat(tempDir.mkdir()).isTrue();
+    File sharedmemory = new File(tempDir, "sharedmemory");
+    assertThat(sharedmemory.createNewFile()).isTrue();
+    FileUtils.write(sharedmemory, "toto");
+    Object fileKey = getFileKey(sharedmemory);
+
+    Object tempDirKey = getFileKey(tempDir);
+    File fileInTempDir = new File(tempDir, "someFile.txt");
+    assertThat(fileInTempDir.createNewFile()).isTrue();
+
+    underTest.reset();
+
+    assertThat(tempDir).exists();
+    assertThat(fileInTempDir).doesNotExist();
+    assertThat(getFileKey(tempDir)).isEqualTo(tempDirKey);
+    assertThat(getFileKey(sharedmemory)).isEqualTo(fileKey);
+    // content of sharedMemory file is reset
+    assertThat(FileUtils.readFileToString(sharedmemory)).isNotEqualTo("toto");
+  }
+
+  @Test
+  public void reset_cleans_the_sharedmemory_file() throws IOException {
+    assertThat(tempDir.mkdir()).isTrue();
+    try (AllProcessesCommands commands = new AllProcessesCommands(tempDir)) {
+      for (int i = 0; i < MAX_PROCESSES; i++) {
+        commands.create(i).setUp();
+      }
+
+      underTest.reset();
+
+      for (int i = 0; i < MAX_PROCESSES; i++) {
+        assertThat(commands.create(i).isUp()).isFalse();
+      }
+    }
+  }
+
+  @CheckForNull
+  private static Object getFileKey(File fileInTempDir) throws IOException {
+    Path path = Paths.get(fileInTempDir.toURI());
+    BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
+    return attrs.fileKey();
+  }
+
+  @Test
+  public void reset_throws_ISE_if_data_dir_is_a_file() throws Exception {
+    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_DATA);
+  }
+
+  @Test
+  public void reset_throws_ISE_if_web_dir_is_a_file() throws Exception {
+    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_WEB);
+  }
+
+  @Test
+  public void reset_throws_ISE_if_logs_dir_is_a_file() throws Exception {
+    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_LOGS);
+  }
+
+  @Test
+  public void reset_throws_ISE_if_temp_dir_is_a_file() throws Exception {
+    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_TEMP);
+  }
+
+  private void resetThrowsISEIfDirIsAFile(String property) throws IOException {
+    File file = new File(homeDir, "zoom.store");
+    assertThat(file.createNewFile()).isTrue();
+    settings.getProps().set(property, file.getAbsolutePath());
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Property '" + property + "' is not valid, not a directory: " + file.getAbsolutePath());
+
+    underTest.reset();
+  }
+
+  @Test
+  public void fail_if_required_directory_is_a_file() throws Exception {
+    // <home>/data is missing
+    FileUtils.forceMkdir(webDir);
+    FileUtils.forceMkdir(logsDir);
+    FileUtils.touch(dataDir);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Property 'sonar.path.data' is not valid, not a directory: " + dataDir.getAbsolutePath());
+
+    underTest.reset();
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppLoggingTest.java
new file mode 100644 (file)
index 0000000..61b5feb
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * 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;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.encoder.Encoder;
+import ch.qos.logback.core.joran.spi.ConsoleTarget;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.logging.LogbackHelper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
+
+public class AppLoggingTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private File logDir;
+
+  private AppSettings settings = new TestAppSettings();
+  private AppLogging underTest = new AppLogging(settings);
+
+  @Before
+  public void setUp() throws Exception {
+    logDir = temp.newFolder();
+    settings.getProps().set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
+  }
+
+  @AfterClass
+  public static void resetLogback() throws Exception {
+    new LogbackHelper().resetFromXml("/logback-test.xml");
+  }
+
+  @Test
+  public void no_writing_to_sonar_log_file_when_running_from_sonar_script() {
+    emulateRunFromSonarScript();
+
+    LoggerContext ctx = underTest.configure();
+
+    ctx.getLoggerList().forEach(AppLoggingTest::verifyNoFileAppender);
+  }
+
+  @Test
+  public void root_logger_only_writes_to_console_with_formatting_when_running_from_sonar_script() {
+    emulateRunFromSonarScript();
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) rootLogger.getAppender("APP_CONSOLE");
+    verifyAppFormattedLogEncoder(consoleAppender.getEncoder());
+    assertThat(rootLogger.iteratorForAppenders()).hasSize(1);
+  }
+
+  @Test
+  public void gobbler_logger_writes_to_console_without_formatting_when_running_from_sonar_script() {
+    emulateRunFromSonarScript();
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
+    verifyGobblerConsoleAppender(gobblerLogger);
+    assertThat(gobblerLogger.iteratorForAppenders()).hasSize(1);
+  }
+
+  @Test
+  public void root_logger_writes_to_console_with_formatting_and_to_sonar_log_file_when_running_from_command_line() {
+    emulateRunFromCommandLine(false);
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    verifyAppConsoleAppender(rootLogger.getAppender("APP_CONSOLE"));
+    verifySonarLogFileAppender(rootLogger.getAppender("file_sonar"));
+    assertThat(rootLogger.iteratorForAppenders()).hasSize(2);
+
+    // verify no other logger writes to sonar.log
+    ctx.getLoggerList()
+      .stream()
+      .filter(logger -> !ROOT_LOGGER_NAME.equals(logger.getName()))
+      .forEach(AppLoggingTest::verifyNoFileAppender);
+  }
+
+  @Test
+  public void gobbler_logger_writes_to_console_without_formatting_when_running_from_command_line() {
+    emulateRunFromCommandLine(false);
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
+    verifyGobblerConsoleAppender(gobblerLogger);
+    assertThat(gobblerLogger.iteratorForAppenders()).hasSize(1);
+  }
+
+  @Test
+  public void root_logger_writes_to_console_with_formatting_and_to_sonar_log_file_when_running_from_ITs() {
+    emulateRunFromCommandLine(true);
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    verifyAppConsoleAppender(rootLogger.getAppender("APP_CONSOLE"));
+    verifySonarLogFileAppender(rootLogger.getAppender("file_sonar"));
+    assertThat(rootLogger.iteratorForAppenders()).hasSize(2);
+
+    ctx.getLoggerList()
+      .stream()
+      .filter(logger -> !ROOT_LOGGER_NAME.equals(logger.getName()))
+      .forEach(AppLoggingTest::verifyNoFileAppender);
+  }
+
+  @Test
+  public void gobbler_logger_writes_to_console_without_formatting_when_running_from_ITs() {
+    emulateRunFromCommandLine(true);
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
+    verifyGobblerConsoleAppender(gobblerLogger);
+    assertThat(gobblerLogger.iteratorForAppenders()).hasSize(1);
+  }
+
+  @Test
+  public void configure_no_rotation_on_sonar_file() {
+    settings.getProps().set("sonar.log.rollingPolicy", "none");
+
+    LoggerContext ctx = underTest.configure();
+
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    Appender<ILoggingEvent> appender = rootLogger.getAppender("file_sonar");
+    assertThat(appender)
+      .isNotInstanceOf(RollingFileAppender.class)
+      .isInstanceOf(FileAppender.class);
+  }
+
+  @Test
+  public void default_level_for_root_logger_is_INFO() {
+    LoggerContext ctx = underTest.configure();
+
+    verifyRootLogLevel(ctx, Level.INFO);
+  }
+
+  @Test
+  public void root_logger_level_changes_with_global_property() {
+    settings.getProps().set("sonar.log.level", "TRACE");
+
+    LoggerContext ctx = underTest.configure();
+
+    verifyRootLogLevel(ctx, Level.TRACE);
+  }
+
+  @Test
+  public void root_logger_level_changes_with_app_property() {
+    settings.getProps().set("sonar.log.level.app", "TRACE");
+
+    LoggerContext ctx = underTest.configure();
+
+    verifyRootLogLevel(ctx, Level.TRACE);
+  }
+
+  @Test
+  public void root_logger_level_is_configured_from_app_property_over_global_property() {
+    settings.getProps().set("sonar.log.level", "TRACE");
+    settings.getProps().set("sonar.log.level.app", "DEBUG");
+
+    LoggerContext ctx = underTest.configure();
+
+    verifyRootLogLevel(ctx, Level.DEBUG);
+  }
+
+  @Test
+  public void root_logger_level_changes_with_app_property_and_is_case_insensitive() {
+    settings.getProps().set("sonar.log.level.app", "debug");
+
+    LoggerContext ctx = underTest.configure();
+
+    verifyRootLogLevel(ctx, Level.DEBUG);
+  }
+
+  @Test
+  public void default_to_INFO_if_app_property_has_invalid_value() {
+    settings.getProps().set("sonar.log.level.app", "DodoDouh!");
+
+    LoggerContext ctx = underTest.configure();
+    verifyRootLogLevel(ctx, Level.INFO);
+  }
+
+  @Test
+  public void fail_with_IAE_if_global_property_unsupported_level() {
+    settings.getProps().set("sonar.log.level", "ERROR");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
+
+    underTest.configure();
+  }
+
+  @Test
+  public void fail_with_IAE_if_app_property_unsupported_level() {
+    settings.getProps().set("sonar.log.level.app", "ERROR");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("log level ERROR in property sonar.log.level.app is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
+
+    underTest.configure();
+  }
+
+  @Test
+  public void no_info_log_from_hazelcast() throws IOException {
+    settings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+    underTest.configure();
+
+    assertThat(
+      LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(false);
+  }
+
+  @Test
+  public void configure_logging_for_hazelcast() throws IOException {
+    settings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+    settings.getProps().set(ProcessProperties.HAZELCAST_LOG_LEVEL, "INFO");
+    underTest.configure();
+
+    assertThat(
+      LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(true);
+    assertThat(
+      LoggerFactory.getLogger("com.hazelcast").isDebugEnabled()).isEqualTo(false);
+  }
+
+  private void emulateRunFromSonarScript() {
+    settings.getProps().set("sonar.wrapped", "true");
+  }
+
+  private void emulateRunFromCommandLine(boolean withAllLogsPrintedToConsole) {
+    if (withAllLogsPrintedToConsole) {
+      settings.getProps().set("sonar.log.console", "true");
+    }
+  }
+
+  private static void verifyNoFileAppender(Logger logger) {
+    Iterator<Appender<ILoggingEvent>> iterator = logger.iteratorForAppenders();
+    while (iterator.hasNext()) {
+      assertThat(iterator.next()).isNotInstanceOf(FileAppender.class);
+    }
+  }
+
+  private void verifySonarLogFileAppender(Appender<ILoggingEvent> appender) {
+    assertThat(appender).isInstanceOf(FileAppender.class);
+    FileAppender fileAppender = (FileAppender) appender;
+    assertThat(fileAppender.getFile()).isEqualTo(new File(logDir, "sonar.log").getAbsolutePath());
+    verifyAppFormattedLogEncoder(fileAppender.getEncoder());
+  }
+
+  private void verifyAppConsoleAppender(Appender<ILoggingEvent> appender) {
+    assertThat(appender).isInstanceOf(ConsoleAppender.class);
+    ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) appender;
+    assertThat(consoleAppender.getTarget()).isEqualTo(ConsoleTarget.SystemOut.getName());
+    verifyAppFormattedLogEncoder(consoleAppender.getEncoder());
+  }
+
+  private void verifyAppFormattedLogEncoder(Encoder<ILoggingEvent> encoder) {
+    verifyFormattedLogEncoder(encoder, "%d{yyyy.MM.dd HH:mm:ss} %-5level app[][%logger{20}] %msg%n");
+  }
+
+  private void verifyGobblerConsoleAppender(Logger logger) {
+    Appender<ILoggingEvent> appender = logger.getAppender("GOBBLER_CONSOLE");
+    assertThat(appender).isInstanceOf(ConsoleAppender.class);
+    ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) appender;
+    assertThat(consoleAppender.getTarget()).isEqualTo(ConsoleTarget.SystemOut.getName());
+    verifyFormattedLogEncoder(consoleAppender.getEncoder(), "%msg%n");
+  }
+
+  private void verifyFormattedLogEncoder(Encoder<ILoggingEvent> encoder, String logPattern) {
+    assertThat(encoder).isInstanceOf(PatternLayoutEncoder.class);
+    PatternLayoutEncoder patternEncoder = (PatternLayoutEncoder) encoder;
+    assertThat(patternEncoder.getPattern()).isEqualTo(logPattern);
+  }
+
+  private void verifyRootLogLevel(LoggerContext ctx, Level expected) {
+    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+    assertThat(rootLogger.getLevel()).isEqualTo(expected);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppReloaderImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppReloaderImplTest.java
new file mode 100644 (file)
index 0000000..972720a
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.AppSettingsLoader;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class AppReloaderImplTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AppSettingsLoader settingsLoader = mock(AppSettingsLoader.class);
+  private FileSystem fs = mock(FileSystem.class);
+  private AppState state = mock(AppState.class);
+  private AppLogging logging = mock(AppLogging.class);
+  private AppReloaderImpl underTest = new AppReloaderImpl(settingsLoader, fs, state, logging);
+
+  @Test
+  public void reload_configuration_then_reset_all() throws IOException {
+    AppSettings settings = new TestAppSettings().set("foo", "bar");
+    AppSettings newSettings = new TestAppSettings()
+      .set("foo", "newBar")
+      .set("newProp", "newVal");
+    when(settingsLoader.load()).thenReturn(newSettings);
+
+    underTest.reload(settings);
+
+    assertThat(settings.getProps().rawProperties())
+      .contains(entry("foo", "newBar"))
+      .contains(entry("newProp", "newVal"));
+    verify(logging).configure();
+    verify(state).reset();
+    verify(fs).reset();
+  }
+
+  @Test
+  public void throw_ISE_if_cluster_is_enabled() throws IOException {
+    AppSettings settings = new TestAppSettings().set(ProcessProperties.CLUSTER_ENABLED, "true");
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Restart is not possible with cluster mode");
+
+    underTest.reload(settings);
+
+    verifyZeroInteractions(logging);
+    verifyZeroInteractions(state);
+    verifyZeroInteractions(fs);
+  }
+
+  @Test
+  public void throw_MessageException_if_path_properties_are_changed() throws IOException {
+    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_DATA);
+    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_LOGS);
+    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_TEMP);
+    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_WEB);
+  }
+
+  @Test
+  public void throw_MessageException_if_cluster_mode_changed() throws IOException {
+    verifyFailureIfPropertyValueChanged(ProcessProperties.CLUSTER_ENABLED);
+  }
+
+  private void verifyFailureIfPropertyValueChanged(String propertyKey) throws IOException {
+    AppSettings settings = new TestAppSettings().set(propertyKey, "val1");
+    AppSettings newSettings = new TestAppSettings()
+      .set(propertyKey, "val2");
+    when(settingsLoader.load()).thenReturn(newSettings);
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Property [" + propertyKey + "] cannot be changed on restart: [val1] => [val2]");
+
+    underTest.reload(settings);
+
+    verifyZeroInteractions(logging);
+    verifyZeroInteractions(state);
+    verifyZeroInteractions(fs);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java
new file mode 100644 (file)
index 0000000..ffffcc0
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.sonar.application.cluster.AppStateClusterImpl;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AppStateFactoryTest {
+
+  private TestAppSettings settings = new TestAppSettings();
+  private AppStateFactory underTest = new AppStateFactory(settings);
+
+  @Test
+  public void create_cluster_implementation_if_cluster_is_enabled() {
+    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_NAME, "foo");
+
+    AppState appState = underTest.create();
+    assertThat(appState).isInstanceOf(AppStateClusterImpl.class);
+    ((AppStateClusterImpl) appState).close();
+  }
+
+  @Test
+  public void cluster_implementation_is_disabled_by_default() {
+    assertThat(underTest.create()).isInstanceOf(AppStateImpl.class);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppStateImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppStateImplTest.java
new file mode 100644 (file)
index 0000000..e49c5f5
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class AppStateImplTest {
+
+  private AppStateListener listener = mock(AppStateListener.class);
+  private AppStateImpl underTest = new AppStateImpl();
+
+  @Test
+  public void get_and_set_operational_flag() {
+    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isFalse();
+    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isFalse();
+    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isFalse();
+
+    underTest.setOperational(ProcessId.ELASTICSEARCH);
+
+    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isFalse();
+    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isTrue();
+    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isFalse();
+
+    // only local mode is supported. App state = local state
+    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, false)).isFalse();
+    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, false)).isTrue();
+    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, false)).isFalse();
+  }
+
+  @Test
+  public void notify_listeners_when_a_process_becomes_operational() {
+    underTest.addListener(listener);
+
+    underTest.setOperational(ProcessId.ELASTICSEARCH);
+
+    verify(listener).onAppStateOperational(ProcessId.ELASTICSEARCH);
+    verifyNoMoreInteractions(listener);
+  }
+
+  @Test
+  public void tryToLockWebLeader_returns_true_if_first_call() {
+    assertThat(underTest.tryToLockWebLeader()).isTrue();
+
+    // next calls return false
+    assertThat(underTest.tryToLockWebLeader()).isFalse();
+    assertThat(underTest.tryToLockWebLeader()).isFalse();
+  }
+
+  @Test
+  public void reset_initializes_all_flags() {
+    underTest.setOperational(ProcessId.ELASTICSEARCH);
+    assertThat(underTest.tryToLockWebLeader()).isTrue();
+
+    underTest.reset();
+
+    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isFalse();
+    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isFalse();
+    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isFalse();
+    assertThat(underTest.tryToLockWebLeader()).isTrue();
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java
new file mode 100644 (file)
index 0000000..f733682
--- /dev/null
@@ -0,0 +1,476 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.EnumMap;
+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;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.application.process.ProcessLauncher;
+import org.sonar.application.process.ProcessMonitor;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+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 static java.util.Collections.synchronizedList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
+import static org.sonar.process.ProcessId.ELASTICSEARCH;
+import static org.sonar.process.ProcessId.WEB_SERVER;
+
+public class SchedulerImplTest {
+
+  @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();
+  private TestCommandFactory javaCommandFactory = new TestCommandFactory();
+  private TestProcessLauncher processLauncher = new TestProcessLauncher();
+  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();
+  }
+
+  @Test
+  public void start_and_stop_sequence_of_ES_WEB_CE_in_order() throws Exception {
+    enableAllProcesses();
+    SchedulerImpl underTest = newScheduler();
+    underTest.schedule();
+
+    // elasticsearch does not have preconditions to start
+    TestProcess es = processLauncher.waitForProcess(ELASTICSEARCH);
+    assertThat(es.isAlive()).isTrue();
+    assertThat(processLauncher.processes).hasSize(1);
+
+    // elasticsearch becomes operational -> web leader is starting
+    es.operational = true;
+    waitForAppStateOperational(ELASTICSEARCH);
+    TestProcess web = processLauncher.waitForProcess(WEB_SERVER);
+    assertThat(web.isAlive()).isTrue();
+    assertThat(processLauncher.processes).hasSize(2);
+    assertThat(processLauncher.commands).containsExactly(esCommand, webLeaderCommand);
+
+    // web becomes operational -> CE is starting
+    web.operational = true;
+    waitForAppStateOperational(WEB_SERVER);
+    TestProcess ce = processLauncher.waitForProcess(COMPUTE_ENGINE);
+    assertThat(ce.isAlive()).isTrue();
+    assertThat(processLauncher.processes).hasSize(3);
+    assertThat(processLauncher.commands).containsExactly(esCommand, webLeaderCommand, ceCommand);
+
+    // all processes are up
+    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isTrue());
+
+    // processes are stopped in reverse order of startup
+    underTest.terminate();
+    assertThat(orderedStops).containsExactly(COMPUTE_ENGINE, WEB_SERVER, ELASTICSEARCH);
+    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
+
+    // does nothing because scheduler is already terminated
+    underTest.awaitTermination();
+  }
+
+  private void enableAllProcesses() {
+    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+  }
+
+  @Test
+  public void all_processes_are_stopped_if_one_process_goes_down() throws Exception {
+    Scheduler underTest = startAll();
+
+    processLauncher.waitForProcess(WEB_SERVER).destroyForcibly();
+
+    underTest.awaitTermination();
+    assertThat(orderedStops).containsExactly(WEB_SERVER, COMPUTE_ENGINE, ELASTICSEARCH);
+    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
+
+    // following does nothing
+    underTest.terminate();
+    underTest.awaitTermination();
+  }
+
+  @Test
+  public void all_processes_are_stopped_if_one_process_fails_to_start() throws Exception {
+    enableAllProcesses();
+    SchedulerImpl underTest = newScheduler();
+    processLauncher.makeStartupFail = COMPUTE_ENGINE;
+
+    underTest.schedule();
+
+    processLauncher.waitForProcess(ELASTICSEARCH).operational = true;
+    processLauncher.waitForProcess(WEB_SERVER).operational = true;
+
+    underTest.awaitTermination();
+    assertThat(orderedStops).containsExactly(WEB_SERVER, ELASTICSEARCH);
+    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
+  }
+
+  @Test
+  public void terminate_can_be_called_multiple_times() throws Exception {
+    Scheduler underTest = startAll();
+
+    underTest.terminate();
+    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
+
+    // does nothing
+    underTest.terminate();
+  }
+
+  @Test
+  public void awaitTermination_blocks_until_all_processes_are_stopped() throws Exception {
+    Scheduler underTest = startAll();
+
+    Thread awaitingTermination = new Thread(() -> underTest.awaitTermination());
+    awaitingTermination.start();
+    assertThat(awaitingTermination.isAlive()).isTrue();
+
+    underTest.terminate();
+    // the thread is being stopped
+    awaitingTermination.join();
+    assertThat(awaitingTermination.isAlive()).isFalse();
+  }
+
+  @Test
+  @Ignore("false-positives on Travis CI")
+  public void restart_reloads_java_commands_and_restarts_all_processes() throws Exception {
+    Scheduler underTest = startAll();
+
+    processLauncher.waitForProcess(WEB_SERVER).askedForRestart = true;
+
+    // waiting for all processes to be stopped
+    boolean stopped = false;
+    while (!stopped) {
+      stopped = orderedStops.size() == 3;
+      Thread.sleep(1L);
+    }
+
+    // restarting
+    verify(appReloader, timeout(60_000)).reload(settings);
+    processLauncher.waitForProcessAlive(ELASTICSEARCH);
+    processLauncher.waitForProcessAlive(COMPUTE_ENGINE);
+    processLauncher.waitForProcessAlive(WEB_SERVER);
+
+    underTest.terminate();
+    // 3+3 processes have been stopped
+    assertThat(orderedStops).hasSize(6);
+    assertThat(processLauncher.waitForProcess(ELASTICSEARCH).isAlive()).isFalse();
+    assertThat(processLauncher.waitForProcess(COMPUTE_ENGINE).isAlive()).isFalse();
+    assertThat(processLauncher.waitForProcess(WEB_SERVER).isAlive()).isFalse();
+
+    // verify that awaitTermination() does not block
+    underTest.awaitTermination();
+  }
+
+  @Test
+  public void restart_stops_all_if_new_settings_are_not_allowed() throws Exception {
+    Scheduler underTest = startAll();
+    doThrow(new IllegalStateException("reload error")).when(appReloader).reload(settings);
+
+    processLauncher.waitForProcess(WEB_SERVER).askedForRestart = true;
+
+    // waiting for all processes to be stopped
+    processLauncher.waitForProcessDown(ELASTICSEARCH);
+    processLauncher.waitForProcessDown(COMPUTE_ENGINE);
+    processLauncher.waitForProcessDown(WEB_SERVER);
+
+    // verify that awaitTermination() does not block
+    underTest.awaitTermination();
+  }
+
+  @Test
+  public void web_follower_starts_only_when_web_leader_is_operational() throws Exception {
+    // leader takes the lock, so underTest won't get it
+    assertThat(appState.tryToLockWebLeader()).isTrue();
+
+    appState.setOperational(ProcessId.ELASTICSEARCH);
+    enableAllProcesses();
+    SchedulerImpl underTest = newScheduler();
+    underTest.schedule();
+
+    processLauncher.waitForProcessAlive(ProcessId.ELASTICSEARCH);
+    assertThat(processLauncher.processes).hasSize(1);
+
+    // leader becomes operational -> follower can start
+    appState.setOperational(ProcessId.WEB_SERVER);
+    processLauncher.waitForProcessAlive(WEB_SERVER);
+
+    underTest.terminate();
+  }
+
+  @Test
+  public void web_server_waits_for_remote_elasticsearch_to_be_started_if_local_es_is_disabled() throws Exception {
+    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+    SchedulerImpl underTest = newScheduler();
+    underTest.schedule();
+
+    // WEB and CE wait for ES to be up
+    assertThat(processLauncher.processes).isEmpty();
+
+    // ES becomes operational on another node -> web leader can start
+    appState.setRemoteOperational(ProcessId.ELASTICSEARCH);
+    processLauncher.waitForProcessAlive(WEB_SERVER);
+    assertThat(processLauncher.processes).hasSize(1);
+
+    underTest.terminate();
+  }
+
+  @Test
+  public void compute_engine_waits_for_remote_elasticsearch_and_web_leader_to_be_started_if_local_es_is_disabled() throws Exception {
+    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
+    SchedulerImpl underTest = newScheduler();
+    underTest.schedule();
+
+    // CE waits for ES and WEB leader to be up
+    assertThat(processLauncher.processes).isEmpty();
+
+    // ES and WEB leader become operational on another nodes -> CE can start
+    appState.setRemoteOperational(ProcessId.ELASTICSEARCH);
+    appState.setRemoteOperational(ProcessId.WEB_SERVER);
+
+    processLauncher.waitForProcessAlive(COMPUTE_ENGINE);
+    assertThat(processLauncher.processes).hasSize(1);
+
+    underTest.terminate();
+  }
+
+  private SchedulerImpl newScheduler() {
+    return new SchedulerImpl(settings, appReloader, javaCommandFactory, processLauncher, appState)
+      .setProcessWatcherDelayMs(1L);
+  }
+
+  private Scheduler startAll() throws InterruptedException {
+    enableAllProcesses();
+    SchedulerImpl scheduler = newScheduler();
+    scheduler.schedule();
+    processLauncher.waitForProcess(ELASTICSEARCH).operational = true;
+    processLauncher.waitForProcess(WEB_SERVER).operational = true;
+    processLauncher.waitForProcess(COMPUTE_ENGINE).operational = true;
+    return scheduler;
+  }
+
+  private void waitForAppStateOperational(ProcessId id) throws InterruptedException {
+    while (true) {
+      if (appState.isOperational(id, true)) {
+        return;
+      }
+      Thread.sleep(1L);
+    }
+  }
+
+  private class TestCommandFactory implements CommandFactory {
+    @Override
+    public EsCommand createEsCommand() {
+      return esCommand;
+    }
+
+    @Override
+    public JavaCommand createWebCommand(boolean leader) {
+      return leader ? webLeaderCommand : webFollowerCommand;
+    }
+
+    @Override
+    public JavaCommand createCeCommand() {
+      return ceCommand;
+    }
+  }
+
+  private class TestProcessLauncher implements ProcessLauncher {
+    private final EnumMap<ProcessId, TestProcess> processes = new EnumMap<>(ProcessId.class);
+    private final List<AbstractCommand<?>> commands = synchronizedList(new ArrayList<>());
+    private ProcessId makeStartupFail = null;
+
+    @Override
+    public ProcessMonitor launch(EsCommand esCommand) {
+      return launchImpl(esCommand);
+    }
+
+    @Override
+    public ProcessMonitor launch(JavaCommand javaCommand) {
+      return launchImpl(javaCommand);
+    }
+
+    private ProcessMonitor launchImpl(AbstractCommand<?> javaCommand) {
+      commands.add(javaCommand);
+      if (makeStartupFail == javaCommand.getProcessId()) {
+        throw new IllegalStateException("cannot start " + javaCommand.getProcessId());
+      }
+      TestProcess process = new TestProcess(javaCommand.getProcessId());
+      processes.put(javaCommand.getProcessId(), process);
+      return process;
+    }
+
+    private TestProcess waitForProcess(ProcessId id) throws InterruptedException {
+      while (true) {
+        TestProcess p = processes.get(id);
+        if (p != null) {
+          return p;
+        }
+        Thread.sleep(1L);
+      }
+    }
+
+    private TestProcess waitForProcessAlive(ProcessId id) throws InterruptedException {
+      while (true) {
+        TestProcess p = processes.get(id);
+        if (p != null && p.isAlive()) {
+          return p;
+        }
+        Thread.sleep(1L);
+      }
+    }
+
+    private TestProcess waitForProcessDown(ProcessId id) throws InterruptedException {
+      while (true) {
+        TestProcess p = processes.get(id);
+        if (p != null && !p.isAlive()) {
+          return p;
+        }
+        Thread.sleep(1L);
+      }
+    }
+
+    @Override
+    public void close() {
+      for (TestProcess process : processes.values()) {
+        process.destroyForcibly();
+      }
+    }
+  }
+
+  private class TestProcess implements ProcessMonitor, AutoCloseable {
+    private final ProcessId processId;
+    private final CountDownLatch alive = new CountDownLatch(1);
+    private boolean operational = false;
+    private boolean askedForRestart = false;
+
+    private TestProcess(ProcessId processId) {
+      this.processId = processId;
+    }
+
+    @Override
+    public InputStream getInputStream() {
+      return mock(InputStream.class, Mockito.RETURNS_MOCKS);
+    }
+
+    @Override
+    public InputStream getErrorStream() {
+      return mock(InputStream.class, Mockito.RETURNS_MOCKS);
+    }
+
+    @Override
+    public void closeStreams() {
+    }
+
+    @Override
+    public boolean isAlive() {
+      return alive.getCount() == 1;
+    }
+
+    @Override
+    public void askForStop() {
+      destroyForcibly();
+    }
+
+    @Override
+    public void destroyForcibly() {
+      if (isAlive()) {
+        orderedStops.add(processId);
+      }
+      alive.countDown();
+    }
+
+    @Override
+    public void waitFor() throws InterruptedException {
+      alive.await();
+    }
+
+    @Override
+    public void waitFor(long timeout, TimeUnit timeoutUnit) throws InterruptedException {
+      alive.await(timeout, timeoutUnit);
+    }
+
+    @Override
+    public boolean isOperational() {
+      return operational;
+    }
+
+    @Override
+    public boolean askedForRestart() {
+      return askedForRestart;
+    }
+
+    @Override
+    public void acknowledgeAskForRestart() {
+      this.askedForRestart = false;
+    }
+
+    @Override
+    public void close() {
+      alive.countDown();
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/TestAppState.java b/server/sonar-main/src/test/java/org/sonar/application/TestAppState.java
new file mode 100644 (file)
index 0000000..d01b129
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nonnull;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+
+public class TestAppState implements AppState {
+
+  private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class);
+  private final Map<ProcessId, Boolean> remoteProcesses = new EnumMap<>(ProcessId.class);
+  private final List<AppStateListener> listeners = new ArrayList<>();
+  private final AtomicBoolean webLeaderLocked = new AtomicBoolean(false);
+
+  @Override
+  public void addListener(@Nonnull AppStateListener listener) {
+    this.listeners.add(listener);
+  }
+
+  @Override
+  public boolean isOperational(ProcessId processId, boolean local) {
+    if (local) {
+      return localProcesses.computeIfAbsent(processId, p -> false);
+    }
+    return remoteProcesses.computeIfAbsent(processId, p -> false);
+  }
+
+  @Override
+  public void setOperational(ProcessId processId) {
+    localProcesses.put(processId, true);
+    remoteProcesses.put(processId, true);
+    listeners.forEach(l -> l.onAppStateOperational(processId));
+  }
+
+  public void setRemoteOperational(ProcessId processId) {
+    remoteProcesses.put(processId, true);
+    listeners.forEach(l -> l.onAppStateOperational(processId));
+  }
+
+  @Override
+  public boolean tryToLockWebLeader() {
+    return webLeaderLocked.compareAndSet(false, true);
+  }
+
+  @Override
+  public void reset() {
+    webLeaderLocked.set(false);
+    localProcesses.clear();
+  }
+
+  @Override
+  public void registerSonarQubeVersion(String sonarqubeVersion) {
+    // nothing to do
+  }
+
+  @Override
+  public Optional<String> getLeaderHostName() {
+    return Optional.of(NetworkUtils.getHostName());
+  }
+
+  @Override
+  public void close() {
+    // nothing to do
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java
new file mode 100644 (file)
index 0000000..fd0497c
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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.cluster;
+
+import com.hazelcast.core.HazelcastInstance;
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.slf4j.Logger;
+import org.sonar.application.AppStateListener;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.sonar.application.cluster.HazelcastTestHelper.createHazelcastClient;
+import static org.sonar.application.cluster.HazelcastTestHelper.newClusterSettings;
+import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
+
+public class AppStateClusterImplTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+  @Test
+  public void instantiation_throws_ISE_if_cluster_mode_is_disabled() throws Exception {
+    TestAppSettings settings = new TestAppSettings();
+    settings.set(ProcessProperties.CLUSTER_ENABLED, "false");
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Cluster is not enabled on this instance");
+
+    new AppStateClusterImpl(settings);
+  }
+
+  @Test
+  public void tryToLockWebLeader_returns_true_only_for_the_first_call() throws Exception {
+    TestAppSettings settings = newClusterSettings();
+
+    try (AppStateClusterImpl underTest = new AppStateClusterImpl(settings)) {
+      assertThat(underTest.tryToLockWebLeader()).isEqualTo(true);
+      assertThat(underTest.tryToLockWebLeader()).isEqualTo(false);
+    }
+  }
+
+  @Test
+  public void log_when_sonarqube_is_joining_a_cluster () throws IOException, InterruptedException, IllegalAccessException, NoSuchFieldException {
+    // Now launch an instance that try to be part of the hzInstance cluster
+    TestAppSettings settings = newClusterSettings();
+
+    Logger logger = mock(Logger.class);
+    AppStateClusterImpl.setLogger(logger);
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      verify(logger).info(
+        eq("Joined the cluster [{}] that contains the following hosts : [{}]"),
+        eq("sonarqube"),
+        anyString()
+      );
+    }
+  }
+
+  @Test
+  public void test_listeners() throws InterruptedException {
+    AppStateListener listener = mock(AppStateListener.class);
+    try (AppStateClusterImpl underTest = new AppStateClusterImpl(newClusterSettings())) {
+      underTest.addListener(listener);
+
+      underTest.setOperational(ProcessId.ELASTICSEARCH);
+      verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
+
+      assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isEqualTo(true);
+      assertThat(underTest.isOperational(ProcessId.APP, true)).isEqualTo(false);
+      assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isEqualTo(false);
+      assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isEqualTo(false);
+    }
+  }
+
+  @Test
+  public void registerSonarQubeVersion_publishes_version_on_first_call() {
+    TestAppSettings settings = newClusterSettings();
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      appStateCluster.registerSonarQubeVersion("6.4.1.5");
+
+      HazelcastInstance hzInstance = createHazelcastClient(appStateCluster);
+      assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get())
+        .isNotNull()
+        .isInstanceOf(String.class)
+        .isEqualTo("6.4.1.5");
+    }
+  }
+
+  @Test
+  public void reset_throws_always_ISE() {
+    TestAppSettings settings = newClusterSettings();
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      expectedException.expect(IllegalStateException.class);
+      expectedException.expectMessage("state reset is not supported in cluster mode");
+      appStateCluster.reset();
+    }
+  }
+
+  @Test
+  public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() throws Exception {
+    // Now launch an instance that try to be part of the hzInstance cluster
+    TestAppSettings settings = newClusterSettings();
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      // Register first version
+      appStateCluster.registerSonarQubeVersion("1.0.0");
+
+      expectedException.expect(IllegalStateException.class);
+      expectedException.expectMessage("The local version 2.0.0 is not the same as the cluster 1.0.0");
+
+      // Registering a second different version must trigger an exception
+      appStateCluster.registerSonarQubeVersion("2.0.0");
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java
new file mode 100644 (file)
index 0000000..5472d57
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.cluster;
+
+import org.junit.Test;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ClusterProcessTest {
+  @Test
+  public void test_equality() {
+    ClusterProcess clusterProcess = new ClusterProcess("A", ProcessId.WEB_SERVER);
+
+    assertThat(clusterProcess)
+      .isNotEqualTo(null)
+      .isEqualTo(clusterProcess)
+      .isNotEqualTo(new ClusterProcess("B", ProcessId.WEB_SERVER))
+      .isNotEqualTo(new ClusterProcess("A", ProcessId.ELASTICSEARCH))
+      .isEqualTo(new ClusterProcess("A", ProcessId.WEB_SERVER));
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java
new file mode 100644 (file)
index 0000000..05e471c
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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.cluster;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.ProcessProperties;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ClusterPropertiesTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AppSettings appSettings = new TestAppSettings();
+
+  @Test
+  public void test_default_values() throws Exception {
+    ClusterProperties props = new ClusterProperties(appSettings);
+
+    assertThat(props.getNetworkInterfaces())
+      .isEqualTo(Collections.emptyList());
+    assertThat(props.getPort())
+      .isEqualTo(9003);
+    assertThat(props.isEnabled())
+      .isEqualTo(false);
+    assertThat(props.getHosts())
+      .isEqualTo(Collections.emptyList());
+    assertThat(props.getName())
+      .isEqualTo("sonarqube");
+  }
+
+  @Test
+  public void test_port_parameter() {
+    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+
+    Stream.of("-50", "0", "65536", "128563").forEach(
+      port -> {
+        appSettings.getProps().set(ProcessProperties.CLUSTER_PORT, port);
+
+        ClusterProperties clusterProperties = new ClusterProperties(appSettings);
+        expectedException.expect(IllegalArgumentException.class);
+        expectedException.expectMessage(
+          String.format("Cluster port have been set to %s which is outside the range [1-65535].", port));
+        clusterProperties.validate();
+
+      });
+  }
+
+  @Test
+  public void test_interfaces_parameter() {
+    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+    appSettings.getProps().set(ProcessProperties.CLUSTER_NETWORK_INTERFACES, "8.8.8.8"); // This IP belongs to Google
+
+    ClusterProperties clusterProperties = new ClusterProperties(appSettings);
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(
+      String.format("Interface %s is not available on this machine.", "8.8.8.8"));
+    clusterProperties.validate();
+  }
+
+  @Test
+  public void validate_does_not_fail_if_cluster_enabled_and_name_specified() {
+    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+
+    ClusterProperties clusterProperties = new ClusterProperties(appSettings);
+    clusterProperties.validate();
+  }
+
+  @Test
+  public void test_members() {
+    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+
+    assertThat(
+      new ClusterProperties(appSettings).getHosts()).isEqualTo(
+        Collections.emptyList());
+
+    appSettings.getProps().set(ProcessProperties.CLUSTER_HOSTS, "192.168.1.1");
+    assertThat(
+      new ClusterProperties(appSettings).getHosts()).isEqualTo(
+        Arrays.asList("192.168.1.1:9003"));
+
+    appSettings.getProps().set(ProcessProperties.CLUSTER_HOSTS, "192.168.1.2:5501");
+    assertThat(
+      new ClusterProperties(appSettings).getHosts()).containsExactlyInAnyOrder(
+        "192.168.1.2:5501");
+
+    appSettings.getProps().set(ProcessProperties.CLUSTER_HOSTS, "192.168.1.2:5501,192.168.1.1");
+    assertThat(
+      new ClusterProperties(appSettings).getHosts()).containsExactlyInAnyOrder(
+        "192.168.1.2:5501", "192.168.1.1:9003");
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java
new file mode 100644 (file)
index 0000000..28103e1
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * 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.cluster;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.core.ItemEvent;
+import com.hazelcast.core.ItemListener;
+import com.hazelcast.core.ReplicatedMap;
+import java.net.InetAddress;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.AfterClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.slf4j.LoggerFactory;
+import org.sonar.application.AppStateListener;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.cluster.ClusterObjectKeys;
+
+import static junit.framework.TestCase.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.sonar.application.cluster.HazelcastTestHelper.closeAllHazelcastClients;
+import static org.sonar.application.cluster.HazelcastTestHelper.createHazelcastClient;
+import static org.sonar.application.cluster.HazelcastTestHelper.newClusterSettings;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
+import static org.sonar.process.cluster.ClusterObjectKeys.LEADER;
+import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
+import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
+
+public class HazelcastClusterTest {
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @AfterClass
+  public static void closeHazelcastClients() {
+    closeAllHazelcastClients();
+  }
+
+  @Test
+  public void test_two_tryToLockWebLeader_must_return_true() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(true);
+      assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(false);
+    }
+  }
+
+  @Test
+  public void when_another_process_locked_webleader_tryToLockWebLeader_must_return_false() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
+      hzInstance.getAtomicReference(LEADER).set("aaaa");
+      assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(false);
+    }
+  }
+
+  @Test
+  public void when_no_leader_getLeaderHostName_must_return_NO_LEADER() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.getLeaderHostName()).isEmpty();
+    }
+  }
+
+  @Test
+  public void when_no_leader_getLeaderHostName_must_return_the_hostname() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.tryToLockWebLeader()).isTrue();
+      assertThat(hzCluster.getLeaderHostName().get()).isEqualTo(NetworkUtils.getHostName());
+    }
+  }
+
+  @Test
+  public void members_must_be_empty_when_there_is_no_other_node() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.getMembers()).isEmpty();
+    }
+  }
+
+  @Test
+  public void set_operational_is_writing_to_cluster() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+
+      hzCluster.setOperational(ProcessId.ELASTICSEARCH);
+
+      assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isTrue();
+      assertThat(hzCluster.isOperational(ProcessId.WEB_SERVER)).isFalse();
+      assertThat(hzCluster.isOperational(ProcessId.COMPUTE_ENGINE)).isFalse();
+
+      // Connect via Hazelcast client to test values
+      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
+      ReplicatedMap<ClusterProcess, Boolean> operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
+      assertThat(operationalProcesses)
+        .containsExactly(new AbstractMap.SimpleEntry<>(new ClusterProcess(hzCluster.getLocalUUID(), ProcessId.ELASTICSEARCH), Boolean.TRUE));
+    }
+  }
+
+  @Test
+  public void cluster_name_comes_from_configuration() {
+    TestAppSettings testAppSettings = newClusterSettings();
+    testAppSettings.set(CLUSTER_NAME, "a_cluster_");
+    ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.getName()).isEqualTo("a_cluster_");
+    }
+  }
+
+  @Test
+  public void cluster_must_keep_a_list_of_clients() throws InterruptedException {
+    TestAppSettings testAppSettings = newClusterSettings();
+    testAppSettings.set(CLUSTER_NAME, "a_cluster_");
+    ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS)).isEmpty();
+      HazelcastInstance hzClient = HazelcastTestHelper.createHazelcastClient(hzCluster);
+      assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS)).containsExactly(hzClient.getLocalEndpoint().getUuid());
+
+      CountDownLatch latch = new CountDownLatch(1);
+      hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS).addItemListener(new ItemListener<Object>() {
+        @Override
+        public void itemAdded(ItemEvent<Object> item) {
+        }
+
+        @Override
+        public void itemRemoved(ItemEvent<Object> item) {
+          latch.countDown();
+        }
+      }, false);
+
+      hzClient.shutdown();
+      if (latch.await(5, TimeUnit.SECONDS)) {
+        assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS).size()).isEqualTo(0);
+      } else {
+        fail("The client UUID have not been removed from the Set within 5 seconds' time lapse");
+      }
+    }
+  }
+
+  @Test
+  public void localUUID_must_not_be_empty() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      assertThat(hzCluster.getLocalUUID()).isNotEmpty();
+    }
+  }
+
+  @Test
+  public void when_a_process_is_set_operational_listener_must_be_triggered() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      AppStateListener listener = mock(AppStateListener.class);
+      hzCluster.addListener(listener);
+
+      // ElasticSearch is not operational
+      assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isFalse();
+
+      // Simulate a node that set ElasticSearch operational
+      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
+      ReplicatedMap<ClusterProcess, Boolean> operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
+      operationalProcesses.put(new ClusterProcess(UUID.randomUUID().toString(), ProcessId.ELASTICSEARCH), Boolean.TRUE);
+      verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
+      verifyNoMoreInteractions(listener);
+
+      // ElasticSearch is operational
+      assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isTrue();
+    }
+  }
+
+
+  @Test
+  public void registerSonarQubeVersion_publishes_version_on_first_call() {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      hzCluster.registerSonarQubeVersion("1.0.0.0");
+
+      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
+      assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get()).isEqualTo("1.0.0.0");
+    }
+  }
+
+  @Test
+  public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() throws Exception {
+    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
+      // Register first version
+      hzCluster.registerSonarQubeVersion("1.0.0");
+
+      expectedException.expect(IllegalStateException.class);
+      expectedException.expectMessage("The local version 2.0.0 is not the same as the cluster 1.0.0");
+
+      // Registering a second different version must trigger an exception
+      hzCluster.registerSonarQubeVersion("2.0.0");
+    }
+  }
+
+  @Test
+  public void simulate_network_cluster() throws InterruptedException {
+    TestAppSettings settings = newClusterSettings();
+    settings.set(ProcessProperties.CLUSTER_NETWORK_INTERFACES, InetAddress.getLoopbackAddress().getHostAddress());
+    AppStateListener listener = mock(AppStateListener.class);
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      appStateCluster.addListener(listener);
+
+      HazelcastInstance hzInstance = createHazelcastClient(appStateCluster.getHazelcastCluster());
+      String uuid = UUID.randomUUID().toString();
+      ReplicatedMap<ClusterProcess, Boolean> replicatedMap = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
+      // process is not up yet --> no events are sent to listeners
+      replicatedMap.put(
+        new ClusterProcess(uuid, ProcessId.ELASTICSEARCH),
+        Boolean.FALSE);
+
+      // process is up yet --> notify listeners
+      replicatedMap.replace(
+        new ClusterProcess(uuid, ProcessId.ELASTICSEARCH),
+        Boolean.TRUE);
+
+      // should be called only once
+      verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
+      verifyNoMoreInteractions(listener);
+
+      hzInstance.shutdown();
+    }
+  }
+
+  @Test
+  public void hazelcast_must_log_through_sl4fj() {
+    MemoryAppender<ILoggingEvent> memoryAppender = new MemoryAppender<>();
+    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+    lc.reset();
+    memoryAppender.setContext(lc);
+    memoryAppender.start();
+    lc.getLogger("com.hazelcast").addAppender(memoryAppender);
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(newClusterSettings())) {
+    }
+
+    assertThat(memoryAppender.events).isNotEmpty();
+    memoryAppender.events.stream().forEach(
+      e -> assertThat(e.getLoggerName()).startsWith("com.hazelcast")
+    );
+  }
+
+  private class MemoryAppender<E> extends AppenderBase<E> {
+    private final List<E> events = new ArrayList();
+
+    @Override
+    protected void append(E eventObject) {
+      events.add(eventObject);
+    }
+  }
+
+
+  @Test
+  public void configuration_tweaks_of_hazelcast_must_be_present() {
+    try (HazelcastCluster hzCluster = HazelcastCluster.create(new ClusterProperties(newClusterSettings()))) {
+      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.tcp.join.port.try.count")).isEqualTo("10");
+      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.phone.home.enabled")).isEqualTo("false");
+      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.logging.type")).isEqualTo("slf4j");
+      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.socket.bind.any")).isEqualTo("false");
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java
new file mode 100644 (file)
index 0000000..76724b3
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.cluster;
+
+import com.hazelcast.client.HazelcastClient;
+import com.hazelcast.client.config.ClientConfig;
+import com.hazelcast.core.HazelcastInstance;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.ProcessProperties;
+
+public class HazelcastTestHelper {
+
+  // Be careful this test won't work if parallel tests is used
+  private static final List<HazelcastInstance> HAZELCAST_INSTANCES = new ArrayList<>();
+
+  static HazelcastInstance createHazelcastClient(HazelcastCluster hzCluster) {
+    ClientConfig clientConfig = new ClientConfig();
+    InetSocketAddress socketAddress = (InetSocketAddress) hzCluster.hzInstance.getLocalEndpoint().getSocketAddress();
+
+    clientConfig.getNetworkConfig().getAddresses().add(
+      String.format("%s:%d",
+        socketAddress.getHostString(),
+        socketAddress.getPort()
+      ));
+    clientConfig.getGroupConfig().setName(hzCluster.getName());
+    HazelcastInstance hazelcastInstance = HazelcastClient.newHazelcastClient(clientConfig);
+    HAZELCAST_INSTANCES.add(hazelcastInstance);
+    return hazelcastInstance;
+  }
+
+  static void closeAllHazelcastClients() {
+    HAZELCAST_INSTANCES.stream().forEach(
+        hz -> {
+          try {
+            hz.shutdown();
+          } catch (Exception ex) {
+            // Ignore it
+          }
+        }
+    );
+  }
+
+  static HazelcastInstance createHazelcastClient(AppStateClusterImpl appStateCluster) {
+    return createHazelcastClient(appStateCluster.getHazelcastCluster());
+  }
+
+  static TestAppSettings newClusterSettings() {
+    TestAppSettings settings = new TestAppSettings();
+    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+    return settings;
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/AppSettingsImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/AppSettingsImplTest.java
new file mode 100644 (file)
index 0000000..2ea6215
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.config;
+
+import java.util.Properties;
+import org.junit.Test;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AppSettingsImplTest {
+
+  @Test
+  public void reload_updates_properties() {
+    Props initialProps = new Props(new Properties());
+    initialProps.set("foo", "bar");
+    Props newProps = new Props(new Properties());
+    newProps.set("foo", "baz");
+    newProps.set("newProp", "newVal");
+
+    AppSettingsImpl underTest = new AppSettingsImpl(initialProps);
+    underTest.reload(newProps);
+
+    assertThat(underTest.getValue("foo").get()).isEqualTo("baz");
+    assertThat(underTest.getValue("newProp").get()).isEqualTo("newVal");
+    assertThat(underTest.getProps().rawProperties()).hasSize(2);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java
new file mode 100644 (file)
index 0000000..d1337a8
--- /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.config;
+
+import java.io.File;
+import org.apache.commons.io.FileUtils;
+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.assertj.core.data.MapEntry.entry;
+
+public class AppSettingsLoaderImplTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void load_properties_from_file() throws Exception {
+    File homeDir = temp.newFolder();
+    File propsFile = new File(homeDir, "conf/sonar.properties");
+    FileUtils.write(propsFile, "foo=bar");
+
+    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[0], homeDir);
+    AppSettings settings = underTest.load();
+
+    assertThat(settings.getProps().rawProperties()).contains(entry("foo", "bar"));
+  }
+
+  @Test
+  public void throws_ISE_if_file_fails_to_be_loaded() throws Exception {
+    File homeDir = temp.newFolder();
+    File propsFileAsDir = new File(homeDir, "conf/sonar.properties");
+    FileUtils.forceMkdir(propsFileAsDir);
+    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[0], homeDir);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Cannot open file " + propsFileAsDir.getAbsolutePath());
+
+    underTest.load();
+  }
+
+  @Test
+  public void file_is_not_loaded_if_it_does_not_exist() throws Exception {
+    File homeDir = temp.newFolder();
+
+    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[0], homeDir);
+    AppSettings settings = underTest.load();
+
+    // no failure, file is ignored
+    assertThat(settings.getProps()).isNotNull();
+  }
+
+  @Test
+  public void command_line_arguments_are_included_to_settings() throws Exception {
+    File homeDir = temp.newFolder();
+
+    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[] {"-Dsonar.foo=bar", "-Dhello=world"}, homeDir);
+    AppSettings settings = underTest.load();
+
+    assertThat(settings.getProps().rawProperties())
+      .contains(entry("sonar.foo", "bar"))
+      .contains(entry("hello", "world"));
+  }
+
+  @Test
+  public void command_line_arguments_make_precedence_over_properties_files() throws Exception {
+    File homeDir = temp.newFolder();
+    File propsFile = new File(homeDir, "conf/sonar.properties");
+    FileUtils.write(propsFile, "sonar.foo=file");
+
+    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[]{"-Dsonar.foo=cli"}, homeDir);
+    AppSettings settings = underTest.load();
+
+    assertThat(settings.getProps().rawProperties()).contains(entry("sonar.foo", "cli"));
+  }
+
+  @Test
+  public void detectHomeDir_returns_existing_dir() throws Exception {
+    assertThat(new AppSettingsLoaderImpl(new String[0]).getHomeDir()).exists().isDirectory();
+
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsLoopbackTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsLoopbackTest.java
new file mode 100644 (file)
index 0000000..de28db4
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * 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.config;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collections;
+import java.util.Enumeration;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.process.MessageException;
+
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NETWORK_INTERFACES;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+import static org.sonar.process.ProcessProperties.SEARCH_HOST;
+
+@RunWith(Theories.class)
+public class ClusterSettingsLoopbackTest {
+
+  private TestAppSettings settings;
+  private static final String LOOPBACK_FORBIDDEN = " must not be a loopback address";
+  private static final String NOT_LOCAL_ADDRESS = " is not a local address";
+  private static final String NOT_RESOLVABLE = " cannot be resolved";
+
+  @DataPoints("parameter")
+  public static final ValueAndResult[] VALID_SINGLE_IP = {
+      // Valid IPs
+      new ValueAndResult("1.2.3.4", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("1.2.3.4:9001", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
+
+      // Valid Name
+      new ValueAndResult("www.sonarqube.org", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("www.google.fr", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("www.google.com, www.sonarsource.com, wwww.sonarqube.org", NOT_LOCAL_ADDRESS),
+
+      new ValueAndResult("...", NOT_RESOLVABLE),
+      new ValueAndResult("භඦආ\uD801\uDC8C\uD801\uDC8B", NOT_RESOLVABLE),
+
+      // Valide IPs List
+      new ValueAndResult("1.2.3.4,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("1.2.3.4:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,1.2.3.4:9001", NOT_LOCAL_ADDRESS),
+      new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccc", NOT_LOCAL_ADDRESS),
+
+      // Loopback IPs
+      new ValueAndResult("localhost", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.0.0.1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.1.1.1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.243.136.241", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("::1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
+
+      // Loopback IPs list
+      new ValueAndResult("127.0.0.1,192.168.11.25", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("192.168.11.25,127.1.1.1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("0:0:0:0:0:0:0:1,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,::1", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("127.0.0.1,192.168.11.25:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("192.168.11.25:9001,127.1.1.1:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[0:0:0:0:0:0:0:1]:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[::1]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
+      new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN)
+  };
+
+  @DataPoints("key")
+  public static final Key[] KEYS = {
+    new Key(SEARCH_HOST, false, false),
+    new Key(CLUSTER_NETWORK_INTERFACES, true, false),
+    new Key(CLUSTER_SEARCH_HOSTS, true, true),
+    new Key(CLUSTER_HOSTS, true, true)
+  };
+
+
+  @DataPoints("unresolvable_hosts")
+  public static final String[] UNRESOLVABLE_HOSTS = {
+  };
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Before
+  public void resetSettings() {
+    settings = getClusterSettings();
+  }
+
+  @Theory
+  public void accept_throws_MessageException(
+    @FromDataPoints("key") Key propertyKey,
+    @FromDataPoints("parameter") ValueAndResult valueAndResult) {
+    // Skip the test if the value is a list and if the key is not accepting a list
+    if (settings == null) {
+      System.out.println("No network found, skipping the test");
+      return;
+    }
+    if ((valueAndResult.isList() && propertyKey.acceptList) || !valueAndResult.isList()) {
+      settings.set(propertyKey.getKey(), valueAndResult.getValue());
+
+      // If the key accept non local IPs there won't be any exception
+      if (!propertyKey.acceptNonLocal || valueAndResult.getMessage() != NOT_LOCAL_ADDRESS) {
+        expectedException.expect(MessageException.class);
+        expectedException.expectMessage(valueAndResult.getMessage());
+      }
+
+      new ClusterSettings().accept(settings.getProps());
+    }
+  }
+
+  private static TestAppSettings getClusterSettings()  {
+    String localAddress = null;
+    try {
+      Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
+      for (NetworkInterface networkInterface : Collections.list(nets)) {
+        if (!networkInterface.isLoopback() && networkInterface.isUp()) {
+          localAddress = networkInterface.getInetAddresses().nextElement().getHostAddress();
+        }
+      }
+      if (localAddress == null) {
+        return null;
+      }
+
+    } catch (SocketException e) {
+      return null;
+    }
+
+    TestAppSettings testAppSettings = new TestAppSettings()
+      .set(CLUSTER_ENABLED, "true")
+      .set(CLUSTER_SEARCH_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
+      .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
+      .set(SEARCH_HOST, localAddress)
+      .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
+    return testAppSettings;
+  }
+
+  private static class Key {
+    private final String key;
+    private final boolean acceptList;
+    private final boolean acceptNonLocal;
+
+    private Key(String key, boolean acceptList, boolean acceptNonLocal) {
+      this.key = key;
+      this.acceptList = acceptList;
+      this.acceptNonLocal = acceptNonLocal;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public boolean acceptList() {
+      return acceptList;
+    }
+
+    public boolean acceptNonLocal() {
+      return acceptNonLocal;
+    }
+  }
+
+  private static class ValueAndResult {
+    private final String value;
+    private final String message;
+
+    private ValueAndResult(String value, String message) {
+      this.value = value;
+      this.message = message;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public boolean isList() {
+      return value != null && value.contains(",");
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
new file mode 100644 (file)
index 0000000..ed89b46
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * 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.config;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessProperties;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
+import static org.sonar.process.ProcessId.ELASTICSEARCH;
+import static org.sonar.process.ProcessId.WEB_SERVER;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_DISABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+import static org.sonar.process.ProcessProperties.SEARCH_HOST;
+
+
+public class ClusterSettingsTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private TestAppSettings settings;
+
+  @Before
+  public void resetSettings() {
+    settings = getClusterSettings();
+  }
+
+  @Test
+  public void test_isClusterEnabled() {
+    settings.set(CLUSTER_ENABLED, "true");
+    assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue();
+
+    settings.set(CLUSTER_ENABLED, "false");
+    assertThat(ClusterSettings.isClusterEnabled(settings)).isFalse();
+  }
+
+  @Test
+  public void isClusterEnabled_returns_false_by_default() {
+    assertThat(ClusterSettings.isClusterEnabled(new TestAppSettings())).isFalse();
+  }
+
+  @Test
+  public void getEnabledProcesses_returns_all_processes_by_default() {
+    assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, ELASTICSEARCH, WEB_SERVER);
+  }
+
+  @Test
+  public void getEnabledProcesses_returns_all_processes_by_default_in_cluster_mode() {
+    settings.set(CLUSTER_ENABLED, "true");
+
+    assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, ELASTICSEARCH, WEB_SERVER);
+  }
+
+  @Test
+  public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() {
+    settings.set(CLUSTER_ENABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+
+    assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, WEB_SERVER);
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_internal_property_for_web_leader_is_configured() {
+    settings.set(CLUSTER_ENABLED, "true");
+    settings.set("sonar.cluster.web.startupLeader", "true");
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Property [sonar.cluster.web.startupLeader] is forbidden");
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_search_enabled_with_loopback() {
+    settings.set(CLUSTER_ENABLED, "true");
+    settings.set(CLUSTER_SEARCH_DISABLED, "false");
+    settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2");
+    settings.set(SEARCH_HOST, "::1");
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("The interface address [::1] of [sonar.search.host] must not be a loopback address");
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  @Test
+  public void accept_not_throwing_MessageException_if_search_disabled_with_loopback() {
+    settings.set(CLUSTER_ENABLED, "true");
+    settings.set(CLUSTER_SEARCH_DISABLED, "true");
+    settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2");
+    settings.set(SEARCH_HOST, "127.0.0.1");
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  @Test
+  public void accept_does_nothing_if_cluster_is_disabled() {
+    settings.set(CLUSTER_ENABLED, "false");
+    // this property is supposed to fail if cluster is enabled
+    settings.set("sonar.cluster.web.startupLeader", "true");
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_h2() {
+    settings.set(CLUSTER_ENABLED, "true");
+    settings.set("sonar.jdbc.url", "jdbc:h2:mem");
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Embedded database is not supported in cluster mode");
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_default_jdbc_url() {
+    settings.clearProperty(JDBC_URL);
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Embedded database is not supported in cluster mode");
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  @Test
+  public void isLocalElasticsearchEnabled_returns_true_by_default() {
+    assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
+  }
+
+  @Test
+  public void isLocalElasticsearchEnabled_returns_true_by_default_in_cluster_mode() {
+    assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
+  }
+
+  @Test
+  public void isLocalElasticsearchEnabled_returns_false_if_local_es_node_is_disabled_in_cluster_mode() {
+    settings.set(CLUSTER_ENABLED, "true");
+    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+
+    assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse();
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_searchHost_is_missing() {
+    settings.clearProperty(SEARCH_HOST);
+    checkMandatoryProperty(SEARCH_HOST);
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_searchHost_is_blank() {
+    settings.set(SEARCH_HOST, " ");
+    checkMandatoryProperty(SEARCH_HOST);
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_clusterHosts_is_missing() {
+    settings.clearProperty(CLUSTER_HOSTS);
+    checkMandatoryProperty(CLUSTER_HOSTS);
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_clusterHosts_is_blank() {
+    settings.set(CLUSTER_HOSTS, " ");
+    checkMandatoryProperty(CLUSTER_HOSTS);
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() {
+    settings.clearProperty(CLUSTER_SEARCH_HOSTS);
+    checkMandatoryProperty(CLUSTER_SEARCH_HOSTS);
+  }
+
+  @Test
+  public void accept_throws_MessageException_if_clusterSearchHosts_is_blank() {
+    settings.set(CLUSTER_SEARCH_HOSTS, " ");
+    checkMandatoryProperty(CLUSTER_SEARCH_HOSTS);
+  }
+
+  private void checkMandatoryProperty(String key) {
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage(format("Property [%s] is mandatory", key));
+
+    new ClusterSettings().accept(settings.getProps());
+  }
+
+  private static TestAppSettings getClusterSettings() {
+    TestAppSettings testAppSettings = new TestAppSettings()
+      .set(CLUSTER_ENABLED, "true")
+      .set(CLUSTER_SEARCH_HOSTS, "localhost")
+      .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
+      .set(SEARCH_HOST, "192.168.233.1")
+      .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
+    return testAppSettings;
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/CommandLineParserTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/CommandLineParserTest.java
new file mode 100644 (file)
index 0000000..d94851e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.config;
+
+import java.util.Properties;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CommandLineParserTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void parseArguments() {
+    System.setProperty("CommandLineParserTest.unused", "unused");
+    System.setProperty("sonar.CommandLineParserTest.used", "used");
+
+    Properties p = CommandLineParser.parseArguments(new String[] {"-Dsonar.foo=bar"});
+
+    // test environment can already declare some system properties prefixed by "sonar."
+    // so we can't test the exact number "2"
+    assertThat(p.size()).isGreaterThanOrEqualTo(2);
+    assertThat(p.getProperty("sonar.foo")).isEqualTo("bar");
+    assertThat(p.getProperty("sonar.CommandLineParserTest.used")).isEqualTo("used");
+
+  }
+
+  @Test
+  public void argumentsToProperties_throws_IAE_if_argument_does_not_start_with_minusD() {
+    Properties p = CommandLineParser.argumentsToProperties(new String[] {"-Dsonar.foo=bar", "-Dsonar.whitespace=foo bar"});
+    assertThat(p).hasSize(2);
+    assertThat(p.getProperty("sonar.foo")).isEqualTo("bar");
+    assertThat(p.getProperty("sonar.whitespace")).isEqualTo("foo bar");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Command-line argument must start with -D, for example -Dsonar.jdbc.username=sonar. Got: sonar.bad=true");
+
+    CommandLineParser.argumentsToProperties(new String[] {"-Dsonar.foo=bar", "sonar.bad=true"});
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java
new file mode 100644 (file)
index 0000000..dd902f0
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.config;
+
+import java.io.File;
+import java.util.Properties;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.ProcessProperties.PATH_DATA;
+import static org.sonar.process.ProcessProperties.PATH_HOME;
+import static org.sonar.process.ProcessProperties.PATH_LOGS;
+import static org.sonar.process.ProcessProperties.PATH_TEMP;
+import static org.sonar.process.ProcessProperties.PATH_WEB;
+
+
+public class FileSystemSettingsTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private FileSystemSettings underTest = new FileSystemSettings();
+  private File homeDir;
+
+  @Before
+  public void setUp() throws Exception {
+    homeDir = temp.newFolder();
+  }
+
+  @Test
+  public void relative_paths_are_converted_to_absolute_paths() throws Exception {
+    Props props = new Props(new Properties());
+    props.set(PATH_HOME, homeDir.getAbsolutePath());
+
+    // relative paths
+    props.set(PATH_DATA, "data");
+    props.set(PATH_LOGS, "logs");
+    props.set(PATH_TEMP, "temp");
+
+    // already absolute paths
+    props.set(PATH_WEB, new File(homeDir, "web").getAbsolutePath());
+
+    underTest.accept(props);
+
+    assertThat(props.nonNullValue(PATH_DATA)).isEqualTo(new File(homeDir, "data").getAbsolutePath());
+    assertThat(props.nonNullValue(PATH_LOGS)).isEqualTo(new File(homeDir, "logs").getAbsolutePath());
+    assertThat(props.nonNullValue(PATH_TEMP)).isEqualTo(new File(homeDir, "temp").getAbsolutePath());
+    assertThat(props.nonNullValue(PATH_WEB)).isEqualTo(new File(homeDir, "web").getAbsolutePath());
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/JdbcSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/JdbcSettingsTest.java
new file mode 100644 (file)
index 0000000..5b4eb94
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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.config;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.util.Properties;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.application.config.JdbcSettings.Provider;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+
+public class JdbcSettingsTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  private JdbcSettings underTest = new JdbcSettings();
+  private File homeDir;
+
+  @Before
+  public void setUp() throws Exception {
+    homeDir = temp.newFolder();
+  }
+
+  @Test
+  public void resolve_H2_provider_when_props_is_empty_and_set_URL_to_default_H2() {
+    Props props = newProps();
+
+    assertThat(underTest.resolveProviderAndEnforceNonnullJdbcUrl(props))
+      .isEqualTo(Provider.H2);
+    assertThat(props.nonNullValue(JDBC_URL)).isEqualTo(String.format("jdbc:h2:tcp://%s:9092/sonar", InetAddress.getLoopbackAddress().getHostAddress()));
+  }
+
+  @Test
+  public void resolve_Oracle_when_jdbc_url_contains_oracle_in_any_case() {
+    checkProviderForUrlAndUnchangedUrl("jdbc:oracle:foo", Provider.ORACLE);
+    checkProviderForUrlAndUnchangedUrl("jdbc:OrAcLe:foo", Provider.ORACLE);
+  }
+
+  @Test
+  public void resolve_MySql_when_jdbc_url_contains_mysql_in_any_case() {
+    checkProviderForUrlAndUnchangedUrl("jdbc:mysql:foo", Provider.MYSQL);
+
+    checkProviderForUrlAndUnchangedUrl("jdbc:mYsQL:foo", Provider.MYSQL);
+  }
+
+  @Test
+  public void resolve_SqlServer_when_jdbc_url_contains_sqlserver_in_any_case() {
+    checkProviderForUrlAndUnchangedUrl("jdbc:sqlserver:foo", Provider.SQLSERVER);
+
+    checkProviderForUrlAndUnchangedUrl("jdbc:SQLSeRVeR:foo", Provider.SQLSERVER);
+  }
+
+  @Test
+  public void resolve_POSTGRESQL_when_jdbc_url_contains_POSTGRESQL_in_any_case() {
+    checkProviderForUrlAndUnchangedUrl("jdbc:postgresql:foo", Provider.POSTGRESQL);
+
+    checkProviderForUrlAndUnchangedUrl("jdbc:POSTGRESQL:foo", Provider.POSTGRESQL);
+  }
+
+  private void checkProviderForUrlAndUnchangedUrl(String url, Provider expected) {
+    Props props = newProps(JDBC_URL, url);
+
+    assertThat(underTest.resolveProviderAndEnforceNonnullJdbcUrl(props)).isEqualTo(expected);
+    assertThat(props.nonNullValue(JDBC_URL)).isEqualTo(url);
+  }
+
+  @Test
+  public void fail_with_MessageException_when_provider_is_not_supported() {
+    Props props = newProps(JDBC_URL, "jdbc:microsoft:sqlserver://localhost");
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Unsupported JDBC driver provider: microsoft");
+
+    underTest.resolveProviderAndEnforceNonnullJdbcUrl(props);
+  }
+
+  @Test
+  public void fail_with_MessageException_when_url_does_not_have_jdbc_prefix() {
+    Props props = newProps(JDBC_URL, "oracle:thin:@localhost/XE");
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Bad format of JDBC URL: oracle:thin:@localhost/XE");
+
+    underTest.resolveProviderAndEnforceNonnullJdbcUrl(props);
+  }
+
+  @Test
+  public void check_mysql_parameters() {
+    // minimal -> ok
+    underTest.checkUrlParameters(Provider.MYSQL,
+      "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8");
+
+    // full -> ok
+    underTest.checkUrlParameters(Provider.MYSQL,
+      "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
+
+    // missing required -> ko
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("JDBC URL must have the property 'useUnicode=true'");
+
+    underTest.checkUrlParameters(Provider.MYSQL, "jdbc:mysql://localhost:3306/sonar?characterEncoding=utf8");
+  }
+
+  @Test
+  public void checkAndComplete_sets_driver_path_for_oracle() throws Exception {
+    File driverFile = new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar");
+    FileUtils.touch(driverFile);
+
+    Props props = newProps(JDBC_URL, "jdbc:oracle:thin:@localhost/XE");
+    underTest.accept(props);
+    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
+  }
+
+  @Test
+  public void sets_driver_path_for_h2() throws Exception {
+    File driverFile = new File(homeDir, "lib/jdbc/h2/h2.jar");
+    FileUtils.touch(driverFile);
+
+    Props props = newProps(JDBC_URL, "jdbc:h2:tcp://localhost:9092/sonar");
+    underTest.accept(props);
+    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
+  }
+
+  @Test
+  public void checkAndComplete_sets_driver_path_for_postgresql() throws Exception {
+    File driverFile = new File(homeDir, "lib/jdbc/postgresql/pg.jar");
+    FileUtils.touch(driverFile);
+
+    Props props = newProps(JDBC_URL, "jdbc:postgresql://localhost/sonar");
+    underTest.accept(props);
+    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
+  }
+
+  @Test
+  public void checkAndComplete_sets_driver_path_for_mssql() throws Exception {
+    File driverFile = new File(homeDir, "lib/jdbc/mssql/sqljdbc4.jar");
+    FileUtils.touch(driverFile);
+
+    Props props = newProps(JDBC_URL, "jdbc:sqlserver://localhost/sonar;SelectMethod=Cursor");
+    underTest.accept(props);
+    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
+  }
+
+  @Test
+  public void driver_file() throws Exception {
+    File driverFile = new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar");
+    FileUtils.touch(driverFile);
+
+    String path = underTest.driverPath(homeDir, Provider.ORACLE);
+    assertThat(path).isEqualTo(driverFile.getAbsolutePath());
+  }
+
+  @Test
+  public void driver_dir_does_not_exist() throws Exception {
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Directory does not exist: extensions/jdbc-driver/oracle");
+
+    underTest.driverPath(homeDir, Provider.ORACLE);
+  }
+
+  @Test
+  public void no_files_in_driver_dir() throws Exception {
+    FileUtils.forceMkdir(new File(homeDir, "extensions/jdbc-driver/oracle"));
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Directory does not contain JDBC driver: extensions/jdbc-driver/oracle");
+
+    underTest.driverPath(homeDir, Provider.ORACLE);
+  }
+
+  @Test
+  public void too_many_files_in_driver_dir() throws Exception {
+    FileUtils.touch(new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc5.jar"));
+    FileUtils.touch(new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar"));
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Directory must contain only one JAR file: extensions/jdbc-driver/oracle");
+
+    underTest.driverPath(homeDir, Provider.ORACLE);
+  }
+
+  private Props newProps(String... params) {
+    Properties properties = new Properties();
+    for (int i = 0; i < params.length; i++) {
+      properties.setProperty(params[i], params[i + 1]);
+      i++;
+    }
+    properties.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
+    return new Props(properties);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java
new file mode 100644 (file)
index 0000000..93c1aa0
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.config;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SonarQubeVersionHelperTest {
+  @Test
+  public void getSonarQubeVersion_must_not_return_an_empty_string() {
+    assertThat(SonarQubeVersionHelper.getSonarqubeVersion()).isNotEmpty();
+  }
+
+  @Test
+  public void getSonarQubeVersion_must_always_return_same_value() {
+    String sonarqubeVersion = SonarQubeVersionHelper.getSonarqubeVersion();
+    for (int i = 0; i < 3; i++) {
+      assertThat(SonarQubeVersionHelper.getSonarqubeVersion()).isEqualTo(sonarqubeVersion);
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/TestAppSettings.java b/server/sonar-main/src/test/java/org/sonar/application/config/TestAppSettings.java
new file mode 100644 (file)
index 0000000..18f063f
--- /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.config;
+
+import java.util.Optional;
+import java.util.Properties;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+/**
+ * Simple implementation of {@link AppSettings} that loads
+ * the default values defined by {@link ProcessProperties}.
+ */
+public class TestAppSettings implements AppSettings {
+
+  private Props props;
+
+  public TestAppSettings() {
+    this.props = new Props(new Properties());
+    ProcessProperties.completeDefaults(this.props);
+  }
+
+  public TestAppSettings set(String key, String value) {
+    this.props.set(key, value);
+    return this;
+  }
+
+  @Override
+  public Props getProps() {
+    return props;
+  }
+
+  @Override
+  public Optional<String> getValue(String key) {
+    return Optional.ofNullable(props.value(key));
+  }
+
+  @Override
+  public void reload(Props copy) {
+    this.props = copy;
+  }
+
+  public void clearProperty(String key) {
+    this.props.rawProperties().remove(key);
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/LifecycleTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/LifecycleTest.java
new file mode 100644 (file)
index 0000000..79c9e47
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.sonar.application.process.Lifecycle;
+import org.sonar.application.process.ProcessLifecycleListener;
+import org.sonar.process.ProcessId;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.application.process.Lifecycle.State.INIT;
+import static org.sonar.application.process.Lifecycle.State.STARTED;
+import static org.sonar.application.process.Lifecycle.State.STARTING;
+import static org.sonar.application.process.Lifecycle.State.STOPPING;
+
+public class LifecycleTest {
+
+  @Test
+  public void initial_state_is_INIT() {
+    Lifecycle lifecycle = new Lifecycle(ProcessId.ELASTICSEARCH, Collections.emptyList());
+    assertThat(lifecycle.getState()).isEqualTo(INIT);
+  }
+
+  @Test
+  public void try_to_move_does_not_support_jumping_states() {
+    TestLifeCycleListener listener = new TestLifeCycleListener();
+    Lifecycle lifecycle = new Lifecycle(ProcessId.ELASTICSEARCH, asList(listener));
+    assertThat(lifecycle.getState()).isEqualTo(INIT);
+    assertThat(listener.states).isEmpty();
+
+    assertThat(lifecycle.tryToMoveTo(STARTED)).isFalse();
+    assertThat(lifecycle.getState()).isEqualTo(INIT);
+    assertThat(listener.states).isEmpty();
+
+    assertThat(lifecycle.tryToMoveTo(STARTING)).isTrue();
+    assertThat(lifecycle.getState()).isEqualTo(STARTING);
+    assertThat(listener.states).containsOnly(STARTING);
+  }
+
+  @Test
+  public void no_state_can_not_move_to_itself() {
+    for (Lifecycle.State state : Lifecycle.State.values()) {
+      assertThat(newLifeCycle(state).tryToMoveTo(state)).isFalse();
+    }
+  }
+
+  @Test
+  public void can_move_to_STOPPING_from_STARTING_STARTED_only() {
+    for (Lifecycle.State state : Lifecycle.State.values()) {
+      TestLifeCycleListener listener = new TestLifeCycleListener();
+      boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STOPPING);
+      if (state == STARTING || state == STARTED) {
+        assertThat(tryToMoveTo).as("from state " + state).isTrue();
+        assertThat(listener.states).containsOnly(STOPPING);
+      } else {
+        assertThat(tryToMoveTo).as("from state " + state).isFalse();
+        assertThat(listener.states).isEmpty();
+      }
+    }
+  }
+
+  @Test
+  public void can_move_to_STARTED_from_STARTING_only() {
+    for (Lifecycle.State state : Lifecycle.State.values()) {
+      TestLifeCycleListener listener = new TestLifeCycleListener();
+      boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STARTED);
+      if (state == STARTING) {
+        assertThat(tryToMoveTo).as("from state " + state).isTrue();
+        assertThat(listener.states).containsOnly(STARTED);
+      } else {
+        assertThat(tryToMoveTo).as("from state " + state).isFalse();
+        assertThat(listener.states).isEmpty();
+      }
+    }
+  }
+
+  private static Lifecycle newLifeCycle(Lifecycle.State state, TestLifeCycleListener... listeners) {
+    return new Lifecycle(ProcessId.ELASTICSEARCH, Arrays.asList(listeners), state);
+  }
+
+  private static final class TestLifeCycleListener implements ProcessLifecycleListener {
+    private final List<Lifecycle.State> states = new ArrayList<>();
+
+    @Override
+    public void onProcessState(ProcessId processId, Lifecycle.State state) {
+      this.states.add(state);
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java
new file mode 100644 (file)
index 0000000..66c60fc
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+public class ProcessCommandsProcessMonitorTest {
+
+  @Test
+  public void ProcessMonitorImpl_is_a_proxy_of_Process() throws Exception {
+    Process process = mock(Process.class, RETURNS_DEEP_STUBS);
+    ProcessCommands commands = mock(ProcessCommands.class, RETURNS_DEEP_STUBS);
+
+    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, ProcessId.WEB_SERVER, commands);
+
+    underTest.waitFor();
+    verify(process).waitFor();
+
+    underTest.closeStreams();
+    verify(process.getErrorStream()).close();
+    verify(process.getInputStream()).close();
+    verify(process.getOutputStream()).close();
+
+    underTest.destroyForcibly();
+    verify(process).destroyForcibly();
+
+    assertThat(underTest.getInputStream()).isNotNull();
+
+    underTest.isAlive();
+    verify(process).isAlive();
+
+    underTest.waitFor(123, TimeUnit.MILLISECONDS);
+    verify(process).waitFor(123, TimeUnit.MILLISECONDS);
+  }
+
+  @Test
+  public void ProcessMonitorImpl_is_a_proxy_of_Commands() throws Exception {
+    Process process = mock(Process.class, RETURNS_DEEP_STUBS);
+    ProcessCommands commands = mock(ProcessCommands.class, RETURNS_DEEP_STUBS);
+
+    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, null, commands);
+
+    underTest.askForStop();
+    verify(commands).askForStop();
+
+    underTest.acknowledgeAskForRestart();
+    verify(commands).acknowledgeAskForRestart();
+
+    underTest.askedForRestart();
+    verify(commands).askedForRestart();
+
+    underTest.isOperational();
+    verify(commands).isOperational();
+  }
+
+  @Test
+  public void closeStreams_ignores_null_stream() {
+    ProcessCommands commands = mock(ProcessCommands.class);
+    Process process = mock(Process.class);
+    when(process.getInputStream()).thenReturn(null);
+
+    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, null, commands);
+
+    // no failures
+    underTest.closeStreams();
+  }
+
+  @Test
+  public void closeStreams_ignores_failure_if_stream_fails_to_be_closed() throws Exception {
+    InputStream stream = mock(InputStream.class);
+    doThrow(new IOException("error")).when(stream).close();
+    Process process = mock(Process.class);
+    when(process.getInputStream()).thenReturn(stream);
+
+    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, null, mock(ProcessCommands.class, Mockito.RETURNS_MOCKS));
+
+    // no failures
+    underTest.closeStreams();
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
new file mode 100644 (file)
index 0000000..a0a4bba
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.ProcessId;
+import org.sonar.process.command.JavaCommand;
+import org.sonar.process.jmvoptions.JvmOptions;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+import static org.mockito.Mockito.RETURNS_MOCKS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ProcessLauncherImplTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AllProcessesCommands commands = mock(AllProcessesCommands.class, RETURNS_MOCKS);
+
+  @Test
+  public void launch_forks_a_new_process() throws Exception {
+    File tempDir = temp.newFolder();
+    TestProcessBuilder processBuilder = new TestProcessBuilder();
+    ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
+    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.setJvmOptions(new JvmOptions<>()
+      .add("-Dfoo=bar")
+      .add("-Dfoo2=bar2"));
+
+    ProcessMonitor monitor = underTest.launch(command);
+
+    assertThat(monitor).isNotNull();
+    assertThat(processBuilder.started).isTrue();
+    assertThat(processBuilder.commands.get(0)).endsWith("java");
+    assertThat(processBuilder.commands).containsSequence(
+      "-Dfoo=bar",
+      "-Dfoo2=bar2",
+      "-cp",
+      "lib/*.class" + System.getProperty("path.separator") + "lib/*.jar",
+      "org.sonarqube.Main");
+    assertThat(processBuilder.dir).isEqualTo(command.getWorkDir());
+    assertThat(processBuilder.redirectErrorStream).isTrue();
+    assertThat(processBuilder.environment)
+      .contains(entry("VAR1", "valueOfVar1"))
+      .containsAllEntriesOf(command.getEnvVariables());
+  }
+
+  @Test
+  public void properties_are_passed_to_command_via_a_temporary_properties_file() throws Exception {
+    File tempDir = temp.newFolder();
+    TestProcessBuilder processBuilder = new TestProcessBuilder();
+    ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
+    JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, temp.newFolder());
+    command.setArgument("foo", "bar");
+    command.setArgument("baz", "woo");
+    command.setJvmOptions(new JvmOptions<>());
+
+    underTest.launch(command);
+
+    String propsFilePath = processBuilder.commands.get(processBuilder.commands.size() - 1);
+    File file = new File(propsFilePath);
+    assertThat(file).exists().isFile();
+    try (FileReader reader = new FileReader(file)) {
+      Properties props = new Properties();
+      props.load(reader);
+      assertThat(props).containsOnly(
+        entry("foo", "bar"),
+        entry("baz", "woo"),
+        entry("process.terminationTimeout", "60000"),
+        entry("process.key", ProcessId.ELASTICSEARCH.getKey()),
+        entry("process.index", String.valueOf(ProcessId.ELASTICSEARCH.getIpcIndex())),
+        entry("process.sharedDir", tempDir.getAbsolutePath()));
+    }
+  }
+
+  @Test
+  public void throw_ISE_if_command_fails() throws IOException {
+    File tempDir = temp.newFolder();
+    ProcessLauncherImpl.ProcessBuilder processBuilder = mock(ProcessLauncherImpl.ProcessBuilder.class, RETURNS_MOCKS);
+    when(processBuilder.start()).thenThrow(new IOException("error"));
+    ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Fail to launch process [es]");
+
+    underTest.launch(new JavaCommand(ProcessId.ELASTICSEARCH, temp.newFolder()));
+  }
+
+  private static class TestProcessBuilder implements ProcessLauncherImpl.ProcessBuilder {
+    private List<String> commands = null;
+    private File dir = null;
+    private Boolean redirectErrorStream = null;
+    private final Map<String, String> environment = new HashMap<>();
+    private boolean started = false;
+
+    @Override
+    public List<String> command() {
+      return commands;
+    }
+
+    @Override
+    public TestProcessBuilder command(List<String> commands) {
+      this.commands = commands;
+      return this;
+    }
+
+    @Override
+    public TestProcessBuilder directory(File dir) {
+      this.dir = dir;
+      return this;
+    }
+
+    @Override
+    public Map<String, String> environment() {
+      return environment;
+    }
+
+    @Override
+    public TestProcessBuilder redirectErrorStream(boolean b) {
+      this.redirectErrorStream = b;
+      return this;
+    }
+
+    @Override
+    public Process start() throws IOException {
+      this.started = true;
+      return mock(Process.class);
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/SQProcessTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/SQProcessTest.java
new file mode 100644 (file)
index 0000000..004ef44
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.InputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.mockito.Mockito;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class SQProcessTest {
+
+  private static final ProcessId A_PROCESS_ID = ProcessId.ELASTICSEARCH;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+  @Test
+  public void initial_state_is_INIT() {
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
+
+    assertThat(underTest.getProcessId()).isEqualTo(A_PROCESS_ID);
+    assertThat(underTest.getState()).isEqualTo(Lifecycle.State.INIT);
+  }
+
+  @Test
+  public void start_and_stop_process() {
+    ProcessLifecycleListener listener = mock(ProcessLifecycleListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addProcessLifecycleListener(listener)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      assertThat(underTest.start(() -> testProcess)).isTrue();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STARTED);
+      assertThat(testProcess.isAlive()).isTrue();
+      assertThat(testProcess.streamsClosed).isFalse();
+      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STARTED);
+
+      testProcess.close();
+      // do not wait next run of watcher threads
+      underTest.refreshState();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
+      assertThat(testProcess.isAlive()).isFalse();
+      assertThat(testProcess.streamsClosed).isTrue();
+      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPED);
+    }
+  }
+
+  @Test
+  public void start_does_not_nothing_if_already_started_once() {
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      assertThat(underTest.start(() -> testProcess)).isTrue();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STARTED);
+
+      assertThat(underTest.start(() -> {throw new IllegalStateException();})).isFalse();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STARTED);
+    }
+  }
+
+  @Test
+  public void start_throws_exception_and_move_to_state_STOPPED_if_execution_of_command_fails() {
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("error");
+
+    underTest.start(() -> {throw new IllegalStateException("error");});
+    assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
+  }
+
+  @Test
+  public void send_event_when_process_is_operational() {
+    ProcessEventListener listener = mock(ProcessEventListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addEventListener(listener)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+
+      testProcess.operational = true;
+      underTest.refreshState();
+
+      verify(listener).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.OPERATIONAL);
+    }
+    verifyNoMoreInteractions(listener);
+  }
+
+  @Test
+  public void operational_event_is_sent_once() {
+    ProcessEventListener listener = mock(ProcessEventListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addEventListener(listener)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+      testProcess.operational = true;
+
+      underTest.refreshState();
+      verify(listener).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.OPERATIONAL);
+
+      // second run
+      underTest.refreshState();
+      verifyNoMoreInteractions(listener);
+    }
+  }
+
+  @Test
+  public void send_event_when_process_requests_for_restart() {
+    ProcessEventListener listener = mock(ProcessEventListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addEventListener(listener)
+      .setWatcherDelayMs(1L)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+
+      testProcess.askedForRestart = true;
+      verify(listener, timeout(10_000)).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.ASK_FOR_RESTART);
+
+      // flag is reset so that next run does not trigger again the event
+      underTest.refreshState();
+      verifyNoMoreInteractions(listener);
+      assertThat(testProcess.askedForRestart).isFalse();
+    }
+  }
+
+  @Test
+  public void stopForcibly_stops_the_process_without_graceful_request_for_stop() {
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+
+      underTest.stopForcibly();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
+      assertThat(testProcess.askedForStop).isFalse();
+      assertThat(testProcess.destroyedForcibly).isTrue();
+
+      // second execution of stopForcibly does nothing. It's still stopped.
+      underTest.stopForcibly();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
+    }
+  }
+
+  @Test
+  public void process_stops_after_graceful_request_for_stop() throws Exception {
+    ProcessLifecycleListener listener = mock(ProcessLifecycleListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addProcessLifecycleListener(listener)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+
+      Thread stopperThread = new Thread(() -> underTest.stop(1, TimeUnit.HOURS));
+      stopperThread.start();
+
+      // thread is blocked until process stopped
+      assertThat(stopperThread.isAlive()).isTrue();
+
+      // wait for the stopper thread to ask graceful stop
+      while (!testProcess.askedForStop) {
+        Thread.sleep(1L);
+      }
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPING);
+      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPING);
+
+      // process stopped
+      testProcess.close();
+
+      // waiting for stopper thread to detect and handle the stop
+      stopperThread.join();
+
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
+      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPED);
+    }
+  }
+
+  @Test
+  public void process_is_stopped_forcibly_if_graceful_stop_is_too_long() throws Exception {
+    ProcessLifecycleListener listener = mock(ProcessLifecycleListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addProcessLifecycleListener(listener)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+
+      underTest.stop(1L, TimeUnit.MILLISECONDS);
+
+      testProcess.waitFor();
+      assertThat(testProcess.askedForStop).isTrue();
+      assertThat(testProcess.destroyedForcibly).isTrue();
+      assertThat(testProcess.isAlive()).isFalse();
+      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
+      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPED);
+    }
+  }
+
+  @Test
+  public void process_requests_are_listened_on_regular_basis() throws Exception {
+    ProcessEventListener listener = mock(ProcessEventListener.class);
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
+      .addEventListener(listener)
+      .setWatcherDelayMs(1L)
+      .build();
+
+    try (TestProcess testProcess = new TestProcess()) {
+      underTest.start(() -> testProcess);
+
+      testProcess.operational = true;
+
+      verify(listener, timeout(1_000L)).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.OPERATIONAL);
+    }
+  }
+
+  @Test
+  public void test_toString() {
+    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
+    assertThat(underTest.toString()).isEqualTo("Process[" + A_PROCESS_ID.getKey() + "]");
+  }
+
+  private static class TestProcess implements ProcessMonitor, AutoCloseable {
+
+    private final CountDownLatch alive = new CountDownLatch(1);
+    private final InputStream inputStream = mock(InputStream.class, Mockito.RETURNS_MOCKS);
+    private final InputStream errorStream = mock(InputStream.class, Mockito.RETURNS_MOCKS);
+    private boolean streamsClosed = false;
+    private boolean operational = false;
+    private boolean askedForRestart = false;
+    private boolean askedForStop = false;
+    private boolean destroyedForcibly = false;
+
+    @Override
+    public InputStream getInputStream() {
+      return inputStream;
+    }
+
+    @Override
+    public InputStream getErrorStream() {
+      return errorStream;
+    }
+
+    @Override
+    public void closeStreams() {
+      streamsClosed = true;
+    }
+
+    @Override
+    public boolean isAlive() {
+      return alive.getCount() == 1;
+    }
+
+    @Override
+    public void askForStop() {
+      askedForStop = true;
+      // do not stop, just asking
+    }
+
+    @Override
+    public void destroyForcibly() {
+      destroyedForcibly = true;
+      alive.countDown();
+    }
+
+    @Override
+    public void waitFor() throws InterruptedException {
+      alive.await();
+    }
+
+    @Override
+    public void waitFor(long timeout, TimeUnit timeoutUnit) throws InterruptedException {
+      alive.await(timeout, timeoutUnit);
+    }
+
+    @Override
+    public boolean isOperational() {
+      return operational;
+    }
+
+    @Override
+    public boolean askedForRestart() {
+      return askedForRestart;
+    }
+
+    @Override
+    public void acknowledgeAskForRestart() {
+      this.askedForRestart = false;
+    }
+
+    @Override
+    public void close() {
+      alive.countDown();
+    }
+  }
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
new file mode 100644 (file)
index 0000000..6d0b972
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.sonar.application.FileSystem;
+import org.sonar.application.Scheduler;
+import org.sonar.application.config.AppSettings;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class StopRequestWatcherImplTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+  private AppSettings settings = mock(AppSettings.class, RETURNS_DEEP_STUBS);
+  private ProcessCommands commands = mock(ProcessCommands.class);
+  private Scheduler scheduler = mock(Scheduler.class);
+
+  @Test
+  public void do_not_watch_command_if_disabled() throws IOException {
+    enableSetting(false);
+    StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(settings, scheduler, commands);
+
+    underTest.startWatching();
+    assertThat(underTest.isAlive()).isFalse();
+
+    underTest.stopWatching();
+    verifyZeroInteractions(commands, scheduler);
+  }
+
+  @Test
+  public void watch_stop_command_if_enabled() throws Exception {
+    enableSetting(true);
+    StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(settings, scheduler, commands);
+    underTest.setDelayMs(1L);
+
+    underTest.startWatching();
+    assertThat(underTest.isAlive()).isTrue();
+    verify(scheduler, never()).terminate();
+
+    when(commands.askedForStop()).thenReturn(true);
+    verify(scheduler, timeout(1_000L)).terminate();
+
+    underTest.stopWatching();
+    while (underTest.isAlive()) {
+      Thread.sleep(1L);
+    }
+  }
+
+  @Test
+  public void create_instance_with_default_delay() throws IOException {
+    FileSystem fs = mock(FileSystem.class);
+    when(fs.getTempDir()).thenReturn(temp.newFolder());
+
+    StopRequestWatcherImpl underTest = StopRequestWatcherImpl.create(settings, scheduler, fs);
+
+    assertThat(underTest.getDelayMs()).isEqualTo(500L);
+  }
+
+  @Test
+  public void stop_watching_commands_if_thread_is_interrupted() throws Exception {
+    enableSetting(true);
+    StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(settings, scheduler, commands);
+
+    underTest.startWatching();
+    underTest.interrupt();
+
+    while (underTest.isAlive()) {
+      Thread.sleep(1L);
+    }
+    assertThat(underTest.isAlive()).isFalse();
+  }
+
+  private void enableSetting(boolean b) {
+    when(settings.getProps().valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND)).thenReturn(b);
+  }
+
+}
diff --git a/server/sonar-main/src/test/java/org/sonar/application/process/StreamGobblerTest.java b/server/sonar-main/src/test/java/org/sonar/application/process/StreamGobblerTest.java
new file mode 100644 (file)
index 0000000..2f1498d
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.application.process;
+
+import java.io.InputStream;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.sonar.application.process.StreamGobbler;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class StreamGobblerTest {
+
+  @Test
+  public void forward_stream_to_log() {
+    InputStream stream = IOUtils.toInputStream("one\nsecond log\nthird log\n");
+    Logger logger = mock(Logger.class);
+
+    StreamGobbler gobbler = new StreamGobbler(stream, "WEB", logger);
+    verifyZeroInteractions(logger);
+
+    gobbler.start();
+    StreamGobbler.waitUntilFinish(gobbler);
+
+    verify(logger).info("one");
+    verify(logger).info("second log");
+    verify(logger).info("third log");
+    verifyNoMoreInteractions(logger);
+  }
+}
diff --git a/server/sonar-main/src/test/resources/logback-test.xml b/server/sonar-main/src/test/resources/logback-test.xml
new file mode 100644 (file)
index 0000000..4c62d57
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration debug="false">
+  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
+
+  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+      <pattern>
+        %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
+      </pattern>
+    </encoder>
+  </appender>
+
+  <root>
+    <level value="INFO"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+
+</configuration>
diff --git a/server/sonar-process-monitor/pom.xml b/server/sonar-process-monitor/pom.xml
deleted file mode 100644 (file)
index 6551c15..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <groupId>org.sonarsource.sonarqube</groupId>
-    <artifactId>server</artifactId>
-    <version>6.6-SNAPSHOT</version>
-    <relativePath>../</relativePath>
-  </parent>
-
-  <artifactId>sonar-process-monitor</artifactId>
-  <name>SonarQube :: Process Monitor</name>
-
-  <properties>
-    <!--
-     version as stored in JAR and displayed in webapp. It is
-     overridden on Travis when replacing SNAPSHOT version by
-     build unique version, for instance "6.3.0.12345".
-     -->
-    <buildVersion>${project.version}</buildVersion>
-  </properties>
-
-  <dependencies>
-    <dependency>
-      <groupId>${project.groupId}</groupId>
-      <artifactId>sonar-process</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-to-slf4j</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-core</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>ch.qos.logback</groupId>
-      <artifactId>logback-classic</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.hazelcast</groupId>
-      <artifactId>hazelcast</artifactId>
-    </dependency>
-    <!--
-    Required by our usage of Guava for clustering : CeWorkerFactoryImpl.getClusteredWorkerUUIDs()
-    -->
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.elasticsearch.client</groupId>
-      <artifactId>transport</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.code.findbugs</groupId>
-      <artifactId>jsr305</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.hazelcast</groupId>
-      <artifactId>hazelcast-client</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <resources>
-      <resource>
-        <!-- Used to resolve variables in file sonarqube-version.txt -->
-        <directory>src/main/resources</directory>
-        <filtering>true</filtering>
-      </resource>
-    </resources>
-  </build>
-</project>
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
deleted file mode 100644 (file)
index f397d60..0000000
+++ /dev/null
@@ -1,135 +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.application;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileVisitOption;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.EnumSet;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.config.AppSettings;
-import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
-
-import static java.lang.String.format;
-import static java.nio.file.FileVisitResult.CONTINUE;
-import static org.apache.commons.io.FileUtils.forceMkdir;
-import static org.sonar.process.FileUtils2.deleteDirectory;
-import static org.sonar.process.ProcessProperties.PATH_DATA;
-import static org.sonar.process.ProcessProperties.PATH_LOGS;
-import static org.sonar.process.ProcessProperties.PATH_TEMP;
-import static org.sonar.process.ProcessProperties.PATH_WEB;
-
-public class AppFileSystem implements FileSystem {
-
-  private static final Logger LOG = LoggerFactory.getLogger(AppFileSystem.class);
-  private static final EnumSet<FileVisitOption> FOLLOW_LINKS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
-
-  private final AppSettings settings;
-
-  public AppFileSystem(AppSettings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  public void reset() throws IOException {
-    createDirectory(PATH_DATA);
-    createDirectory(PATH_WEB);
-    createDirectory(PATH_LOGS);
-    File tempDir = createOrCleanTempDirectory(PATH_TEMP);
-    try (AllProcessesCommands allProcessesCommands = new AllProcessesCommands(tempDir)) {
-      allProcessesCommands.clean();
-    }
-  }
-
-  @Override
-  public File getTempDir() {
-    return settings.getProps().nonNullValueAsFile(PATH_TEMP);
-  }
-
-  private boolean createDirectory(String propKey) throws IOException {
-    File dir = settings.getProps().nonNullValueAsFile(propKey);
-    if (dir.exists()) {
-      ensureIsNotAFile(propKey, dir);
-      return false;
-    }
-
-    forceMkdir(dir);
-    ensureIsNotAFile(propKey, dir);
-    return true;
-  }
-
-  private static void ensureIsNotAFile(String propKey, File dir) {
-    if (!dir.isDirectory()) {
-      throw new IllegalStateException(format("Property '%s' is not valid, not a directory: %s",
-        propKey, dir.getAbsolutePath()));
-    }
-  }
-
-  private File createOrCleanTempDirectory(String propKey) throws IOException {
-    File dir = settings.getProps().nonNullValueAsFile(propKey);
-    LOG.info("Cleaning or creating temp directory {}", dir.getAbsolutePath());
-    if (!createDirectory(propKey)) {
-      Files.walkFileTree(dir.toPath(), FOLLOW_LINKS, CleanTempDirFileVisitor.VISIT_MAX_DEPTH, new CleanTempDirFileVisitor(dir.toPath()));
-    }
-    return dir;
-  }
-
-  private static class CleanTempDirFileVisitor extends SimpleFileVisitor<Path> {
-    private static final Path SHAREDMEMORY_FILE = Paths.get("sharedmemory");
-    static final int VISIT_MAX_DEPTH = 1;
-
-    private final Path path;
-    private final boolean symLink;
-
-    CleanTempDirFileVisitor(Path path) {
-      this.path = path;
-      this.symLink = Files.isSymbolicLink(path);
-    }
-
-    @Override
-    public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
-      File file = filePath.toFile();
-      if (file.isDirectory()) {
-        deleteDirectory(file);
-      } else if (filePath.getFileName().equals(SHAREDMEMORY_FILE)) {
-        return CONTINUE;
-      } else if (!symLink || !filePath.equals(path)) {
-        Files.delete(filePath);
-      }
-      return CONTINUE;
-    }
-
-    @Override
-    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-      if (!dir.equals(path)) {
-        deleteDirectory(dir.toFile());
-      }
-      return CONTINUE;
-    }
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java
deleted file mode 100644 (file)
index 5e19aab..0000000
+++ /dev/null
@@ -1,236 +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.application;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.FileAppender;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.process.StreamGobbler;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.logging.LogLevelConfig;
-import org.sonar.process.logging.LogbackHelper;
-import org.sonar.process.logging.RootLoggerConfig;
-
-import static org.slf4j.Logger.ROOT_LOGGER_NAME;
-import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
-import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
-
-/**
- * Configure logback for the APP process.
- *
- * <p>
- * SonarQube's logging use cases:
- * <ol>
- *   <li>
- *     SQ started as a background process (with {@code sonar.sh start}):
- *     <ul>
- *       <li>
- *         logs produced by the JVM before logback is setup in the APP JVM or which can't be caught by logback
- *         (such as JVM crash) must be written to sonar.log
- *       </li>
- *       <li>
- *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
- *         by logback (such as JVM crash) must be written to sonar.log
- *       </li>
- *       <li>each JVM writes its own logs into its dedicated file</li>
- *     </ul>
- *   </li>
- *   <li>
- *     SQ started in console with wrapper (ie. with {@code sonar.sh console}):
- *     <ul>
- *       <li>
- *         logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback
- *         (such as JVM crash) must be written to sonar.log
- *       </li>
- *       <li>
- *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
- *         by logback (such as JVM crash) must be written to sonar.log
- *       </li>
- *       <li>each JVM writes its own logs into its dedicated file</li>
- *       <li>APP JVM logs are written to the APP JVM {@code System.out}</li>
- *     </ul>
- *   </li>
- *   <li>
- *     SQ started from command line (ie. {@code java -jar sonar-application-X.Y.jar}):
- *     <ul>
- *       <li>
- *         logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback
- *         (such as JVM crash) are the responsibility of the user to be dealt with
- *       </li>
- *       <li>
- *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
- *         by logback (such as JVM crash) must be written to APP's {@code System.out}
- *       </li>
- *       <li>each JVM writes its own logs into its dedicated file</li>
- *       <li>APP JVM logs are written to the APP JVM {@code System.out}</li>
- *     </ul>
- *   </li>
- *   <li>
- *     SQ started from an IT (ie. from command line with {@code option -Dsonar.log.console=true}):
- *     <ul>
- *       <li>
- *         logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback
- *         (such as JVM crash) are the responsibility of the developer or maven to be dealt with
- *       </li>
- *       <li>
- *         logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught
- *         by logback (such as JVM crash) must be written to APP's {@code System.out} and are the responsibility of the
- *         developer or maven to be dealt with
- *       </li>
- *       <li>each JVM writes its own logs into its dedicated file</li>
- *       <li>logs of all 4 JVMs are also written to the APP JVM {@code System.out}</li>
- *     </ul>
- *   </li>
- * </ol>
- * </p>
- *
- */
-public class AppLogging {
-
-  private static final String CONSOLE_LOGGER = "console";
-  private static final String CONSOLE_PLAIN_APPENDER = "CONSOLE";
-  private static final String APP_CONSOLE_APPENDER = "APP_CONSOLE";
-  private static final String GOBBLER_PLAIN_CONSOLE = "GOBBLER_CONSOLE";
-  private static final RootLoggerConfig APP_ROOT_LOGGER_CONFIG = newRootLoggerConfigBuilder()
-    .setProcessId(ProcessId.APP)
-    .build();
-
-  private final LogbackHelper helper = new LogbackHelper();
-  private final AppSettings appSettings;
-
-  public AppLogging(AppSettings appSettings) {
-    this.appSettings = appSettings;
-  }
-
-  public LoggerContext configure() {
-    LoggerContext ctx = helper.getRootContext();
-    ctx.reset();
-
-    helper.enableJulChangePropagation(ctx);
-
-    configureConsole(ctx);
-    if (helper.isAllLogsToConsoleEnabled(appSettings.getProps()) || !appSettings.getProps().valueAsBoolean("sonar.wrapped", false)) {
-      configureWithLogbackWritingToFile(ctx);
-    } else {
-      configureWithWrapperWritingToFile(ctx);
-    }
-    helper.apply(
-      LogLevelConfig.newBuilder(helper.getRootLoggerName())
-        .rootLevelFor(ProcessId.APP)
-        .immutableLevel("com.hazelcast",
-          Level.toLevel(
-            appSettings.getProps().nonNullValue(ProcessProperties.HAZELCAST_LOG_LEVEL)))
-        .build(),
-      appSettings.getProps());
-
-    return ctx;
-  }
-
-  /**
-   * Creates a non additive logger dedicated to printing message as is (ie. assuming they are already formatted).
-   *
-   * It creates a dedicated appender to the System.out which applies no formatting the logs it receives.
-   */
-  private void configureConsole(LoggerContext loggerContext) {
-    ConsoleAppender<ILoggingEvent> consoleAppender = helper.newConsoleAppender(loggerContext, CONSOLE_PLAIN_APPENDER, "%msg%n");
-
-    Logger consoleLogger = loggerContext.getLogger(CONSOLE_LOGGER);
-    consoleLogger.setAdditive(false);
-    consoleLogger.addAppender(consoleAppender);
-  }
-
-  /**
-   * The process has been started by orchestrator (ie. via {@code java -jar} and optionally passing the option {@code -Dsonar.log.console=true}).
-   * Therefor, APP's System.out (and System.err) are <strong>not</strong> copied to sonar.log by the wrapper and
-   * printing to sonar.log must be done at logback level.
-   */
-  private void configureWithLogbackWritingToFile(LoggerContext ctx) {
-    // configure all logs (ie. root logger) to be written to sonar.log and also to the console with formatting
-    // in practice, this will be only APP's own logs as logs from sub processes LOGGER_GOBBLER and LOGGER_GOBBLER
-    // is configured below to be detached from root
-    // so, this will make all APP's log to be both written to sonar.log and visible in the console
-    configureRootWithLogbackWritingToFile(ctx);
-
-    // if option -Dsonar.log.console=true has been set, sub processes will write their logs to their own files but also
-    // copy them to their System.out.
-    // otherwise, the only logs to be expected in LOGGER_GOBBLER are those before logback is setup in subprocesses or
-    // when their JVM crashes
-    // they must be printed to App's System.out as is (as they are already formatted)
-    // logger is configured to be non additive as we don't want these logs to be written to sonar.log and duplicated in
-    // the console (with an incorrect formatting)
-    configureGobbler(ctx);
-  }
-
-  /**
-   * SQ has been started by the wrapper (ie. with sonar.sh) therefor, APP's System.out (and System.err) are written to
-   * sonar.log by the wrapper.
-   */
-  private void configureWithWrapperWritingToFile(LoggerContext ctx) {
-    // configure all logs (ie. root logger) to be written to console with formatting
-    // in practice, this will be only APP's own logs as logs from sub processes are written to LOGGER_GOBBLER and
-    // LOGGER_GOBBLER is configured below to be detached from root
-    // logs are written to the console because we want them to be in sonar.log and the wrapper will write any log
-    // from APP's System.out and System.err to sonar.log
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    rootLogger.addAppender(createAppConsoleAppender(ctx, helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG)));
-
-    // in regular configuration, sub processes are not copying their logs to their System.out, so, the only logs to be
-    // expected in LOGGER_GOBBLER are those before logback is setup in subprocesses or when JVM crashes
-    // so, they must be printed to App's System.out as is (as they are already formatted) and the wrapper will write
-    // them to sonar.log
-    // logger is configured to be non additive as we don't want these logs written to sonar.log and duplicated in the
-    // console with an incorrect formatting
-    configureGobbler(ctx);
-  }
-
-  private void configureRootWithLogbackWritingToFile(LoggerContext ctx) {
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    String appLogPattern = helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG);
-    FileAppender<ILoggingEvent> fileAppender = helper.newFileAppender(ctx, appSettings.getProps(), APP_ROOT_LOGGER_CONFIG, appLogPattern);
-    rootLogger.addAppender(fileAppender);
-    rootLogger.addAppender(createAppConsoleAppender(ctx, appLogPattern));
-  }
-
-  /**
-   * Configure the logger to which logs from sub processes are written to
-   * (called {@link StreamGobbler#LOGGER_GOBBLER}) by {@link StreamGobbler},
-   * to be:
-   * <ol>
-   *   <li>non additive (ie. these logs will be output by the appender of {@link StreamGobbler#LOGGER_GOBBLER} and only this one)</li>
-   *   <li>write logs as is (ie. without any extra formatting)</li>
-   *   <li>write exclusively to App's System.out</li>
-   * </ol>
-   */
-  private void configureGobbler(LoggerContext ctx) {
-    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
-    gobblerLogger.setAdditive(false);
-    gobblerLogger.addAppender(helper.newConsoleAppender(ctx, GOBBLER_PLAIN_CONSOLE, "%msg%n"));
-  }
-
-  private ConsoleAppender<ILoggingEvent> createAppConsoleAppender(LoggerContext ctx, String appLogPattern) {
-    return helper.newConsoleAppender(ctx, APP_CONSOLE_APPENDER, appLogPattern);
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloader.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloader.java
deleted file mode 100644 (file)
index f475e10..0000000
+++ /dev/null
@@ -1,36 +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.application;
-
-import java.io.IOException;
-import org.sonar.application.config.AppSettings;
-
-/**
- * Reload settings, reset logging and file system when a
- * server restart has been requested.
- */
-public interface AppReloader {
-
-  /**
-   * This method is called when server is down.
-   */
-  void reload(AppSettings settings) throws IOException;
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloaderImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloaderImpl.java
deleted file mode 100644 (file)
index 3463c1e..0000000
+++ /dev/null
@@ -1,81 +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.application;
-
-import java.io.IOException;
-import java.util.Objects;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.AppSettingsLoader;
-import org.sonar.application.config.ClusterSettings;
-import org.sonar.process.MessageException;
-import org.sonar.process.Props;
-
-import static java.lang.String.format;
-import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
-import static org.sonar.process.ProcessProperties.PATH_DATA;
-import static org.sonar.process.ProcessProperties.PATH_LOGS;
-import static org.sonar.process.ProcessProperties.PATH_TEMP;
-import static org.sonar.process.ProcessProperties.PATH_WEB;
-
-public class AppReloaderImpl implements AppReloader {
-
-  private final AppSettingsLoader settingsLoader;
-  private final FileSystem fileSystem;
-  private final AppState appState;
-  private final AppLogging logging;
-
-  public AppReloaderImpl(AppSettingsLoader settingsLoader, FileSystem fileSystem, AppState appState, AppLogging logging) {
-    this.settingsLoader = settingsLoader;
-    this.fileSystem = fileSystem;
-    this.appState = appState;
-    this.logging = logging;
-  }
-
-  @Override
-  public void reload(AppSettings settings) throws IOException {
-    if (ClusterSettings.isClusterEnabled(settings)) {
-      throw new IllegalStateException("Restart is not possible with cluster mode");
-    }
-    AppSettings reloaded = settingsLoader.load();
-    ensureUnchangedConfiguration(settings.getProps(), reloaded.getProps());
-    settings.reload(reloaded.getProps());
-
-    fileSystem.reset();
-    logging.configure();
-    appState.reset();
-  }
-
-  private static void ensureUnchangedConfiguration(Props oldProps, Props newProps) {
-    verifyUnchanged(oldProps, newProps, PATH_DATA);
-    verifyUnchanged(oldProps, newProps, PATH_WEB);
-    verifyUnchanged(oldProps, newProps, PATH_LOGS);
-    verifyUnchanged(oldProps, newProps, PATH_TEMP);
-    verifyUnchanged(oldProps, newProps, CLUSTER_ENABLED);
-  }
-
-  private static void verifyUnchanged(Props initialProps, Props newProps, String propKey) {
-    String initialValue = initialProps.nonNullValue(propKey);
-    String newValue = newProps.nonNullValue(propKey);
-    if (!Objects.equals(initialValue, newValue)) {
-      throw new MessageException(format("Property [%s] cannot be changed on restart: [%s] => [%s]", propKey, initialValue, newValue));
-    }
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java
deleted file mode 100644 (file)
index 43cb69e..0000000
+++ /dev/null
@@ -1,59 +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.application;
-
-import java.util.Optional;
-import org.sonar.process.ProcessId;
-
-public interface AppState extends AutoCloseable {
-
-  void addListener(AppStateListener listener);
-
-  /**
-   * Whether the process with the specified {@code processId}
-   * has been marked as operational.
-   *
-   * If parameter {@code local} is {@code true}, then only the
-   * process on the local node is requested.
-   *
-   * If parameter {@code local} is {@code false}, then only
-   * the processes on remote nodes are requested, excluding
-   * the local node. In this case at least one process must
-   * be marked as operational.
-   */
-  boolean isOperational(ProcessId processId, boolean local);
-
-  /**
-   * Mark local process as operational. In cluster mode, this
-   * event is propagated to all nodes.
-   */
-  void setOperational(ProcessId processId);
-
-  boolean tryToLockWebLeader();
-
-  void reset();
-
-  void registerSonarQubeVersion(String sonarqubeVersion);
-
-  Optional<String> getLeaderHostName();
-
-  @Override
-  void close();
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateFactory.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateFactory.java
deleted file mode 100644 (file)
index 196aa2a..0000000
+++ /dev/null
@@ -1,37 +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.application;
-
-import org.sonar.application.cluster.AppStateClusterImpl;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.ClusterSettings;
-
-public class AppStateFactory {
-
-  private final AppSettings settings;
-
-  public AppStateFactory(AppSettings settings) {
-    this.settings = settings;
-  }
-
-  public AppState create() {
-    return ClusterSettings.isClusterEnabled(settings) ? new AppStateClusterImpl(settings) : new AppStateImpl();
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java
deleted file mode 100644 (file)
index 9c5e03c..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.application;
-
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import javax.annotation.Nonnull;
-import org.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-
-public class AppStateImpl implements AppState {
-
-  private final Map<ProcessId, Boolean> processes = new EnumMap<>(ProcessId.class);
-  private final List<AppStateListener> listeners = new ArrayList<>();
-  private final AtomicBoolean webLeaderLocked = new AtomicBoolean(false);
-
-  @Override
-  public void addListener(@Nonnull AppStateListener listener) {
-    this.listeners.add(listener);
-  }
-
-  @Override
-  public boolean isOperational(ProcessId processId, boolean local) {
-    return processes.computeIfAbsent(processId, p -> false);
-  }
-
-  @Override
-  public void setOperational(ProcessId processId) {
-    processes.put(processId, true);
-    listeners.forEach(l -> l.onAppStateOperational(processId));
-  }
-
-  @Override
-  public boolean tryToLockWebLeader() {
-    return webLeaderLocked.compareAndSet(false, true);
-  }
-
-  @Override
-  public void reset() {
-    webLeaderLocked.set(false);
-    processes.clear();
-  }
-
-  @Override
-  public void registerSonarQubeVersion(String sonarqubeVersion) {
-    // Nothing to do on non clustered version
-  }
-
-  @Override
-  public Optional<String> getLeaderHostName() {
-    return Optional.of(NetworkUtils.getHostName());
-  }
-
-  @Override
-  public void close() {
-    // nothing to do
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateListener.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateListener.java
deleted file mode 100644 (file)
index 581ea40..0000000
+++ /dev/null
@@ -1,34 +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.application;
-
-import org.sonar.process.ProcessId;
-
-@FunctionalInterface
-public interface AppStateListener {
-
-  /**
-   * The method is called when the state is changed. When cluster
-   * mode is enabled, the event may be raised from another node.
-   *
-   * Listener must subscribe to {@link AppState#addListener(AppStateListener)}.
-   */
-  void onAppStateOperational(ProcessId processId);
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/FileSystem.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/FileSystem.java
deleted file mode 100644 (file)
index efdbcd2..0000000
+++ /dev/null
@@ -1,31 +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.application;
-
-import java.io.File;
-import java.io.IOException;
-
-public interface FileSystem {
-
-  void reset() throws IOException;
-
-  File getTempDir();
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/NodeLifecycle.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/NodeLifecycle.java
deleted file mode 100644 (file)
index 62e09a6..0000000
+++ /dev/null
@@ -1,99 +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.application;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.Map;
-import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.sonar.application.NodeLifecycle.State.INIT;
-import static org.sonar.application.NodeLifecycle.State.OPERATIONAL;
-import static org.sonar.application.NodeLifecycle.State.STARTING;
-import static org.sonar.application.NodeLifecycle.State.STOPPED;
-import static org.sonar.application.NodeLifecycle.State.STOPPING;
-
-/**
- * Lifecycle of the cluster node, consolidating the states
- * of child processes.
- */
-class NodeLifecycle {
-  private static final Logger LOG = LoggerFactory.getLogger(NodeLifecycle.class);
-
-  enum State {
-    // initial state, does nothing
-    INIT,
-
-    // at least one process is still starting
-    STARTING,
-
-    // all the processes are started and operational
-    OPERATIONAL,
-
-    // at least one process is still stopping
-    STOPPING,
-
-    // all processes are stopped
-    STOPPED
-  }
-
-  private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
-
-  private State state = INIT;
-
-  private static Map<State, Set<State>> buildTransitions() {
-    Map<State, Set<State>> res = new EnumMap<>(State.class);
-    res.put(INIT, toSet(STARTING));
-    res.put(STARTING, toSet(OPERATIONAL, STOPPING, STOPPED));
-    res.put(OPERATIONAL, toSet(STOPPING, STOPPED));
-    res.put(STOPPING, toSet(STOPPED));
-    res.put(STOPPED, toSet(STARTING));
-    return res;
-  }
-
-  private static Set<State> toSet(State... states) {
-    if (states.length == 0) {
-      return Collections.emptySet();
-    }
-    if (states.length == 1) {
-      return Collections.singleton(states[0]);
-    }
-    return EnumSet.copyOf(Arrays.asList(states));
-  }
-
-  State getState() {
-    return state;
-  }
-
-  synchronized boolean tryToMoveTo(State to) {
-    boolean res = false;
-    State currentState = state;
-    if (TRANSITIONS.get(currentState).contains(to)) {
-      this.state = to;
-      res = true;
-    }
-    LOG.trace("tryToMoveTo from {} to {} => {}", currentState, to, res);
-    return res;
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/Scheduler.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/Scheduler.java
deleted file mode 100644 (file)
index f69b61d..0000000
+++ /dev/null
@@ -1,35 +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.application;
-
-public interface Scheduler {
-
-  void schedule();
-
-  /**
-   * Stops all processes and waits for them to be down.
-   */
-  void terminate();
-
-  /**
-   * Blocks until all processes are down
-   */
-  void awaitTermination();
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/SchedulerImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/SchedulerImpl.java
deleted file mode 100644 (file)
index 7311297..0000000
+++ /dev/null
@@ -1,315 +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.application;
-
-import java.util.EnumMap;
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Supplier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.ClusterSettings;
-import org.sonar.process.command.CommandFactory;
-import org.sonar.process.command.EsCommand;
-import org.sonar.process.command.JavaCommand;
-import org.sonar.application.process.ProcessLauncher;
-import org.sonar.application.process.Lifecycle;
-import org.sonar.application.process.ProcessEventListener;
-import org.sonar.application.process.ProcessLifecycleListener;
-import org.sonar.application.process.ProcessMonitor;
-import org.sonar.application.process.SQProcess;
-import org.sonar.process.ProcessId;
-
-public class SchedulerImpl implements Scheduler, ProcessEventListener, ProcessLifecycleListener, AppStateListener {
-
-  private static final Logger LOG = LoggerFactory.getLogger(SchedulerImpl.class);
-
-  private final AppSettings settings;
-  private final AppReloader appReloader;
-  private final CommandFactory commandFactory;
-  private final ProcessLauncher processLauncher;
-  private final AppState appState;
-  private final NodeLifecycle nodeLifecycle = new NodeLifecycle();
-
-  private final CountDownLatch keepAlive = new CountDownLatch(1);
-  private final AtomicBoolean restartRequested = new AtomicBoolean(false);
-  private final AtomicBoolean restartDisabled = new AtomicBoolean(false);
-  private final EnumMap<ProcessId, SQProcess> processesById = new EnumMap<>(ProcessId.class);
-  private final AtomicInteger operationalCountDown = new AtomicInteger();
-  private final AtomicInteger stopCountDown = new AtomicInteger(0);
-  private StopperThread stopperThread;
-  private RestarterThread restarterThread;
-  private long processWatcherDelayMs = SQProcess.DEFAULT_WATCHER_DELAY_MS;
-
-  public SchedulerImpl(AppSettings settings, AppReloader appReloader, CommandFactory commandFactory,
-    ProcessLauncher processLauncher,
-    AppState appState) {
-    this.settings = settings;
-    this.appReloader = appReloader;
-    this.commandFactory = commandFactory;
-    this.processLauncher = processLauncher;
-    this.appState = appState;
-    this.appState.addListener(this);
-  }
-
-  SchedulerImpl setProcessWatcherDelayMs(long l) {
-    this.processWatcherDelayMs = l;
-    return this;
-  }
-
-  @Override
-  public void schedule() {
-    if (!nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STARTING)) {
-      return;
-    }
-    processesById.clear();
-
-    for (ProcessId processId : ClusterSettings.getEnabledProcesses(settings)) {
-      SQProcess process = SQProcess.builder(processId)
-        .addProcessLifecycleListener(this)
-        .addEventListener(this)
-        .setWatcherDelayMs(processWatcherDelayMs)
-        .build();
-      processesById.put(process.getProcessId(), process);
-    }
-    operationalCountDown.set(processesById.size());
-
-    tryToStartAll();
-  }
-
-  private void tryToStartAll() {
-    tryToStartEs();
-    tryToStartWeb();
-    tryToStartCe();
-  }
-
-  private void tryToStartEs() {
-    SQProcess process = processesById.get(ProcessId.ELASTICSEARCH);
-    if (process != null) {
-      tryToStartEsProcess(process, commandFactory::createEsCommand);
-    }
-  }
-
-  private void tryToStartWeb() {
-    SQProcess process = processesById.get(ProcessId.WEB_SERVER);
-    if (process == null || !isEsClientStartable()) {
-      return;
-    }
-    if (appState.isOperational(ProcessId.WEB_SERVER, false)) {
-      tryToStartJavaProcess(process, () -> commandFactory.createWebCommand(false));
-    } else if (appState.tryToLockWebLeader()) {
-      tryToStartJavaProcess(process, () -> commandFactory.createWebCommand(true));
-    } else {
-      Optional<String> leader = appState.getLeaderHostName();
-      if (leader.isPresent()) {
-        LOG.info("Waiting for initialization from " + leader.get());
-      } else {
-        LOG.error("Initialization failed. All nodes must be restarted");
-      }
-    }
-  }
-
-  private void tryToStartCe() {
-    SQProcess process = processesById.get(ProcessId.COMPUTE_ENGINE);
-    if (process != null && appState.isOperational(ProcessId.WEB_SERVER, false) && isEsClientStartable()) {
-      tryToStartJavaProcess(process, commandFactory::createCeCommand);
-    }
-  }
-
-  private boolean isEsClientStartable() {
-    boolean requireLocalEs = ClusterSettings.isLocalElasticsearchEnabled(settings);
-    return appState.isOperational(ProcessId.ELASTICSEARCH, requireLocalEs);
-  }
-
-  private void tryToStartJavaProcess(SQProcess process, Supplier<JavaCommand> commandSupplier) {
-    tryToStart(process, () -> {
-      JavaCommand command = commandSupplier.get();
-      return processLauncher.launch(command);
-    });
-  }
-
-  private void tryToStartEsProcess(SQProcess process, Supplier<EsCommand> commandSupplier) {
-    tryToStart(process, () -> {
-      EsCommand command = commandSupplier.get();
-      return processLauncher.launch(command);
-    });
-  }
-
-  private void tryToStart(SQProcess process, Supplier<ProcessMonitor> processMonitorSupplier) {
-    try {
-      process.start(processMonitorSupplier);
-    } catch (RuntimeException e) {
-      // failed to start command -> stop everything
-      terminate();
-      throw e;
-    }
-  }
-
-  private void stopAll() {
-    // order is important for non-cluster mode
-    stopProcess(ProcessId.COMPUTE_ENGINE);
-    stopProcess(ProcessId.WEB_SERVER);
-    stopProcess(ProcessId.ELASTICSEARCH);
-  }
-
-  /**
-   * Request for graceful stop then blocks until process is stopped.
-   * Returns immediately if the process is disabled in configuration.
-   */
-  private void stopProcess(ProcessId processId) {
-    SQProcess process = processesById.get(processId);
-    if (process != null) {
-      process.stop(1, TimeUnit.MINUTES);
-    }
-  }
-
-  /**
-   * Blocks until all processes are stopped. Pending restart, if
-   * any, is disabled.
-   */
-  @Override
-  public void terminate() {
-    // disable ability to request for restart
-    restartRequested.set(false);
-    restartDisabled.set(true);
-
-    if (nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STOPPING)) {
-      LOG.info("Stopping SonarQube");
-    }
-    stopAll();
-    if (stopperThread != null) {
-      stopperThread.interrupt();
-    }
-    if (restarterThread != null) {
-      restarterThread.interrupt();
-    }
-    keepAlive.countDown();
-  }
-
-  @Override
-  public void awaitTermination() {
-    try {
-      keepAlive.await();
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-    }
-  }
-
-  @Override
-  public void onProcessEvent(ProcessId processId, Type type) {
-    if (type == Type.OPERATIONAL) {
-      onProcessOperational(processId);
-    } else if (type == Type.ASK_FOR_RESTART && restartRequested.compareAndSet(false, true)) {
-      stopAsync();
-    }
-  }
-
-  private void onProcessOperational(ProcessId processId) {
-    LOG.info("Process[{}] is up", processId.getKey());
-    appState.setOperational(processId);
-    if (operationalCountDown.decrementAndGet() == 0 && nodeLifecycle.tryToMoveTo(NodeLifecycle.State.OPERATIONAL)) {
-      LOG.info("SonarQube is up");
-    }
-  }
-
-  @Override
-  public void onAppStateOperational(ProcessId processId) {
-    if (nodeLifecycle.getState() == NodeLifecycle.State.STARTING) {
-      tryToStartAll();
-    }
-  }
-
-  @Override
-  public void onProcessState(ProcessId processId, Lifecycle.State to) {
-    switch (to) {
-      case STOPPED:
-        onProcessStop(processId);
-        break;
-      case STARTING:
-        stopCountDown.incrementAndGet();
-        break;
-      default:
-        // Nothing to do
-        break;
-    }
-  }
-
-  private void onProcessStop(ProcessId processId) {
-    LOG.info("Process [{}] is stopped", processId.getKey());
-    if (stopCountDown.decrementAndGet() == 0 && nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STOPPED)) {
-      if (!restartDisabled.get() &&
-        restartRequested.compareAndSet(true, false)) {
-        LOG.info("SonarQube is restarting");
-        restartAsync();
-      } else {
-        LOG.info("SonarQube is stopped");
-        // all processes are stopped, no restart requested
-        // Let's clean-up resources
-        terminate();
-      }
-
-    } else if (nodeLifecycle.tryToMoveTo(NodeLifecycle.State.STOPPING)) {
-      // this is the first process stopping
-      stopAsync();
-    }
-  }
-
-  private void stopAsync() {
-    stopperThread = new StopperThread();
-    stopperThread.start();
-  }
-
-  private void restartAsync() {
-    restarterThread = new RestarterThread();
-    restarterThread.start();
-  }
-
-  private class RestarterThread extends Thread {
-    public RestarterThread() {
-      super("Restarter");
-    }
-
-    @Override
-    public void run() {
-      try {
-        appReloader.reload(settings);
-        schedule();
-      } catch (Exception e) {
-        LOG.error("Fail to restart", e);
-        terminate();
-      }
-    }
-  }
-
-  private class StopperThread extends Thread {
-    public StopperThread() {
-      super("Stopper");
-    }
-
-    @Override
-    public void run() {
-      stopAll();
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java
deleted file mode 100644 (file)
index 5e5f2b0..0000000
+++ /dev/null
@@ -1,116 +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.application.cluster;
-
-import java.util.EnumMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.AppState;
-import org.sonar.application.AppStateListener;
-import org.sonar.application.config.AppSettings;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-
-public class AppStateClusterImpl implements AppState {
-  private static Logger LOGGER = LoggerFactory.getLogger(AppStateClusterImpl.class);
-
-  private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class);
-  private final HazelcastCluster hazelcastCluster;
-
-  public AppStateClusterImpl(AppSettings appSettings) {
-    ClusterProperties clusterProperties = new ClusterProperties(appSettings);
-    clusterProperties.validate();
-
-    if (!clusterProperties.isEnabled()) {
-      throw new IllegalStateException("Cluster is not enabled on this instance");
-    }
-
-    hazelcastCluster = HazelcastCluster.create(clusterProperties);
-    // Add the local endpoint to be used by processes
-    appSettings.getProps().set(ProcessProperties.CLUSTER_LOCALENDPOINT, hazelcastCluster.getLocalEndPoint());
-    appSettings.getProps().set(ProcessProperties.CLUSTER_MEMBERUUID, hazelcastCluster.getLocalUUID());
-
-    String members = hazelcastCluster.getMembers().stream().collect(Collectors.joining(","));
-    LOGGER.info("Joined the cluster [{}] that contains the following hosts : [{}]", hazelcastCluster.getName(), members);
-  }
-
-  @Override
-  public void addListener(@Nonnull AppStateListener listener) {
-    hazelcastCluster.addListener(listener);
-  }
-
-  @Override
-  public boolean isOperational(@Nonnull ProcessId processId, boolean local) {
-    if (local) {
-      return localProcesses.computeIfAbsent(processId, p -> false);
-    }
-    return hazelcastCluster.isOperational(processId);
-  }
-
-  @Override
-  public void setOperational(@Nonnull ProcessId processId) {
-    localProcesses.put(processId, true);
-    hazelcastCluster.setOperational(processId);
-  }
-
-  @Override
-  public boolean tryToLockWebLeader() {
-    return hazelcastCluster.tryToLockWebLeader();
-  }
-
-  @Override
-  public void reset() {
-    throw new IllegalStateException("state reset is not supported in cluster mode");
-  }
-
-  @Override
-  public void close() {
-    hazelcastCluster.close();
-  }
-
-  @Override
-  public void registerSonarQubeVersion(String sonarqubeVersion) {
-    hazelcastCluster.registerSonarQubeVersion(sonarqubeVersion);
-  }
-
-  @Override
-  public Optional<String> getLeaderHostName() {
-    return hazelcastCluster.getLeaderHostName();
-  }
-
-  HazelcastCluster getHazelcastCluster() {
-    return hazelcastCluster;
-  }
-
-  /**
-   * Only used for testing purpose
-   *
-   * @param logger
-   */
-  static void setLogger(Logger logger) {
-    AppStateClusterImpl.LOGGER = logger;
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProcess.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProcess.java
deleted file mode 100644 (file)
index b9b3340..0000000
+++ /dev/null
@@ -1,66 +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.application.cluster;
-
-import java.io.Serializable;
-import org.sonar.process.ProcessId;
-
-import static java.util.Objects.requireNonNull;
-
-public class ClusterProcess implements Serializable {
-  private final ProcessId processId;
-  private final String nodeUuid;
-
-  public ClusterProcess(String nodeUuid, ProcessId processId) {
-    this.processId = requireNonNull(processId);
-    this.nodeUuid = requireNonNull(nodeUuid);
-  }
-
-  public ProcessId getProcessId() {
-    return processId;
-  }
-
-  public String getNodeUuid() {
-    return nodeUuid;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    ClusterProcess that = (ClusterProcess) o;
-    if (processId != that.processId) {
-      return false;
-    }
-    return nodeUuid.equals(that.nodeUuid);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = processId.hashCode();
-    result = 31 * result + nodeUuid.hashCode();
-    return result;
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProperties.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProperties.java
deleted file mode 100644 (file)
index f33877f..0000000
+++ /dev/null
@@ -1,157 +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.application.cluster;
-
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.config.AppSettings;
-import org.sonar.process.ProcessProperties;
-
-/**
- * Properties of the cluster configuration
- */
-public final class ClusterProperties {
-  static final String DEFAULT_PORT = "9003";
-  private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class);
-
-  private final int port;
-  private final boolean enabled;
-  private final List<String> hosts;
-  private final List<String> networkInterfaces;
-  private final String name;
-
-  ClusterProperties(AppSettings appSettings) {
-    port = appSettings.getProps().valueAsInt(ProcessProperties.CLUSTER_PORT);
-    enabled = appSettings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_ENABLED);
-    networkInterfaces = extractNetworkInterfaces(
-      appSettings.getProps().value(ProcessProperties.CLUSTER_NETWORK_INTERFACES, "")
-    );
-    name = appSettings.getProps().nonNullValue(ProcessProperties.CLUSTER_NAME);
-    hosts = extractHosts(
-      appSettings.getProps().value(ProcessProperties.CLUSTER_HOSTS, "")
-    );
-  }
-
-  int getPort() {
-    return port;
-  }
-
-  boolean isEnabled() {
-    return enabled;
-  }
-
-  List<String> getHosts() {
-    return hosts;
-  }
-
-  List<String> getNetworkInterfaces() {
-    return networkInterfaces;
-  }
-
-  String getName() {
-    return name;
-  }
-
-  void validate() {
-    if (!enabled) {
-      return;
-    }
-
-    // Test validity of port
-    checkArgument(
-      port > 0 && port < 65_536,
-      "Cluster port have been set to %d which is outside the range [1-65535].",
-      port
-    );
-
-    // Test the networkInterfaces parameter
-    try {
-      List<String> localInterfaces = findAllLocalIPs();
-
-      networkInterfaces.forEach(
-        inet -> checkArgument(
-          StringUtils.isEmpty(inet) || localInterfaces.contains(inet),
-          "Interface %s is not available on this machine.",
-          inet
-        )
-      );
-    } catch (SocketException e) {
-      LOGGER.warn("Unable to retrieve network networkInterfaces. Interfaces won't be checked", e);
-    }
-  }
-
-  private static List<String> extractHosts(final String hosts) {
-    List<String> result = new ArrayList<>();
-    for (String host : hosts.split(",")) {
-      if (StringUtils.isNotEmpty(host)) {
-        if (!host.contains(":")) {
-          result.add(
-            String.format("%s:%s", host, DEFAULT_PORT)
-          );
-        } else {
-          result.add(host);
-        }
-      }
-    }
-    return result;
-  }
-
-  private static List<String> extractNetworkInterfaces(final String networkInterfaces) {
-    List<String> result = new ArrayList<>();
-    for (String iface : networkInterfaces.split(",")) {
-      if (StringUtils.isNotEmpty(iface)) {
-        result.add(iface);
-      }
-    }
-    return result;
-  }
-
-  private static List<String> findAllLocalIPs() throws SocketException {
-    Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
-    List<String> localInterfaces = new ArrayList<>();
-
-    while (netInterfaces.hasMoreElements()) {
-      NetworkInterface networkInterface = netInterfaces.nextElement();
-      Enumeration<InetAddress> ips = networkInterface.getInetAddresses();
-      while (ips.hasMoreElements()) {
-        InetAddress ip = ips.nextElement();
-        localInterfaces.add(ip.getHostAddress());
-      }
-    }
-    return localInterfaces;
-  }
-
-  private static void checkArgument(boolean expression,
-                                    @Nullable String messageTemplate,
-                                    @Nullable Object... args) {
-    if (!expression) {
-      throw new IllegalArgumentException(String.format(messageTemplate, args));
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/HazelcastCluster.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/HazelcastCluster.java
deleted file mode 100644 (file)
index a1e4f77..0000000
+++ /dev/null
@@ -1,280 +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.application.cluster;
-
-import com.hazelcast.config.Config;
-import com.hazelcast.config.JoinConfig;
-import com.hazelcast.config.NetworkConfig;
-import com.hazelcast.core.Client;
-import com.hazelcast.core.ClientListener;
-import com.hazelcast.core.EntryEvent;
-import com.hazelcast.core.EntryListener;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
-import com.hazelcast.core.HazelcastInstanceNotActiveException;
-import com.hazelcast.core.IAtomicReference;
-import com.hazelcast.core.ILock;
-import com.hazelcast.core.MapEvent;
-import com.hazelcast.core.Member;
-import com.hazelcast.core.ReplicatedMap;
-import com.hazelcast.nio.Address;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.AppStateListener;
-import org.sonar.process.ProcessId;
-
-import static java.util.stream.Collectors.toList;
-import static org.sonar.process.NetworkUtils.getHostName;
-import static org.sonar.process.cluster.ClusterObjectKeys.CLIENT_UUIDS;
-import static org.sonar.process.cluster.ClusterObjectKeys.HOSTNAME;
-import static org.sonar.process.cluster.ClusterObjectKeys.LEADER;
-import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
-import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
-
-public class HazelcastCluster implements AutoCloseable {
-  private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastCluster.class);
-
-  private final List<AppStateListener> listeners = new ArrayList<>();
-  private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
-  private final String operationalProcessListenerUUID;
-  private final String clientListenerUUID;
-
-  protected final HazelcastInstance hzInstance;
-
-  private HazelcastCluster(Config hzConfig) {
-    // Create the Hazelcast instance
-    hzInstance = Hazelcast.newHazelcastInstance(hzConfig);
-
-    // Get or create the replicated map
-    operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
-    operationalProcessListenerUUID = operationalProcesses.addEntryListener(new OperationalProcessListener());
-    clientListenerUUID = hzInstance.getClientService().addClientListener(new ConnectedClientListener());
-  }
-
-  String getLocalUUID() {
-    return hzInstance.getLocalEndpoint().getUuid();
-  }
-
-  String getName() {
-    return hzInstance.getConfig().getGroupConfig().getName();
-  }
-
-  List<String> getMembers() {
-    return hzInstance.getCluster().getMembers().stream()
-      .filter(m -> !m.localMember())
-      .map(m -> m.getStringAttribute(HOSTNAME))
-      .collect(toList());
-  }
-
-  void addListener(AppStateListener listener) {
-    listeners.add(listener);
-  }
-
-  boolean isOperational(ProcessId processId) {
-    for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
-      if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  void setOperational(ProcessId processId) {
-    operationalProcesses.put(new ClusterProcess(getLocalUUID(), processId), Boolean.TRUE);
-  }
-
-  boolean tryToLockWebLeader() {
-    IAtomicReference<String> leader = hzInstance.getAtomicReference(LEADER);
-    if (leader.get() == null) {
-      ILock lock = hzInstance.getLock(LEADER);
-      lock.lock();
-      try {
-        if (leader.get() == null) {
-          leader.set(getLocalUUID());
-          return true;
-        } else {
-          return false;
-        }
-      } finally {
-        lock.unlock();
-      }
-    } else {
-      return false;
-    }
-  }
-
-  public void registerSonarQubeVersion(String sonarqubeVersion) {
-    IAtomicReference<String> sqVersion = hzInstance.getAtomicReference(SONARQUBE_VERSION);
-    if (sqVersion.get() == null) {
-      ILock lock = hzInstance.getLock(SONARQUBE_VERSION);
-      lock.lock();
-      try {
-        if (sqVersion.get() == null) {
-          sqVersion.set(sonarqubeVersion);
-        }
-      } finally {
-        lock.unlock();
-      }
-    }
-
-    String clusterVersion = sqVersion.get();
-    if (!sqVersion.get().equals(sonarqubeVersion)) {
-      throw new IllegalStateException(
-        String.format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion)
-      );
-    }
-  }
-
-  @Override
-  public void close() {
-    if (hzInstance != null) {
-      try {
-        // Removing listeners
-        operationalProcesses.removeEntryListener(operationalProcessListenerUUID);
-        hzInstance.getClientService().removeClientListener(clientListenerUUID);
-
-        // Removing the operationalProcess from the replicated map
-        operationalProcesses.keySet().forEach(
-          clusterNodeProcess -> {
-            if (clusterNodeProcess.getNodeUuid().equals(getLocalUUID())) {
-              operationalProcesses.remove(clusterNodeProcess);
-            }
-          });
-
-        // Shutdown Hazelcast properly
-        hzInstance.shutdown();
-      } catch (HazelcastInstanceNotActiveException e) {
-        // hazelcastCluster may be already closed by the shutdown hook
-        LOGGER.debug("Unable to close Hazelcast cluster", e);
-      }
-    }
-  }
-
-  public static HazelcastCluster create(ClusterProperties clusterProperties) {
-    Config hzConfig = new Config();
-    hzConfig.getGroupConfig().setName(clusterProperties.getName());
-
-    // Configure the network instance
-    NetworkConfig netConfig = hzConfig.getNetworkConfig();
-    netConfig
-      .setPort(clusterProperties.getPort())
-      .setReuseAddress(true);
-
-    if (!clusterProperties.getNetworkInterfaces().isEmpty()) {
-      netConfig.getInterfaces()
-        .setEnabled(true)
-        .setInterfaces(clusterProperties.getNetworkInterfaces());
-    }
-
-    // Only allowing TCP/IP configuration
-    JoinConfig joinConfig = netConfig.getJoin();
-    joinConfig.getAwsConfig().setEnabled(false);
-    joinConfig.getMulticastConfig().setEnabled(false);
-    joinConfig.getTcpIpConfig().setEnabled(true);
-    joinConfig.getTcpIpConfig().setMembers(clusterProperties.getHosts());
-
-    // Tweak HazelCast configuration
-    hzConfig
-      // Increase the number of tries
-      .setProperty("hazelcast.tcp.join.port.try.count", "10")
-      // Don't bind on all interfaces
-      .setProperty("hazelcast.socket.bind.any", "false")
-      // Don't phone home
-      .setProperty("hazelcast.phone.home.enabled", "false")
-      // Use slf4j for logging
-      .setProperty("hazelcast.logging.type", "slf4j");
-
-    // Trying to resolve the hostname
-    hzConfig.getMemberAttributeConfig().setStringAttribute(HOSTNAME, getHostName());
-
-    // We are not using the partition group of Hazelcast, so disabling it
-    hzConfig.getPartitionGroupConfig().setEnabled(false);
-    return new HazelcastCluster(hzConfig);
-  }
-
-  Optional<String> getLeaderHostName() {
-    String leaderId = (String) hzInstance.getAtomicReference(LEADER).get();
-    if (leaderId != null) {
-      Optional<Member> leader = hzInstance.getCluster().getMembers().stream().filter(m -> m.getUuid().equals(leaderId)).findFirst();
-      if (leader.isPresent()) {
-        return Optional.of(leader.get().getStringAttribute(HOSTNAME));
-      }
-    }
-    return Optional.empty();
-  }
-
-  String getLocalEndPoint() {
-    Address localAddress = hzInstance.getCluster().getLocalMember().getAddress();
-    return String.format("%s:%d", localAddress.getHost(), localAddress.getPort());
-  }
-
-  private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
-    @Override
-    public void entryAdded(EntryEvent<ClusterProcess, Boolean> event) {
-      if (event.getValue()) {
-        listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
-      }
-    }
-
-    @Override
-    public void entryRemoved(EntryEvent<ClusterProcess, Boolean> event) {
-      // Ignore it
-    }
-
-    @Override
-    public void entryUpdated(EntryEvent<ClusterProcess, Boolean> event) {
-      if (event.getValue()) {
-        listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
-      }
-    }
-
-    @Override
-    public void entryEvicted(EntryEvent<ClusterProcess, Boolean> event) {
-      // Ignore it
-    }
-
-    @Override
-    public void mapCleared(MapEvent event) {
-      // Ignore it
-    }
-
-    @Override
-    public void mapEvicted(MapEvent event) {
-      // Ignore it
-    }
-  }
-
-  private class ConnectedClientListener implements ClientListener {
-    @Override
-    public void clientConnected(Client client) {
-      hzInstance.getSet(CLIENT_UUIDS).add(client.getUuid());
-    }
-
-    @Override
-    public void clientDisconnected(Client client) {
-      hzInstance.getSet(CLIENT_UUIDS).remove(client.getUuid());
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/package-info.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/package-info.java
deleted file mode 100644 (file)
index 4501fba..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.application.cluster;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettings.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettings.java
deleted file mode 100644 (file)
index 784e7b1..0000000
+++ /dev/null
@@ -1,32 +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.application.config;
-
-import java.util.Optional;
-import org.sonar.process.Props;
-
-public interface AppSettings {
-
-  Props getProps();
-
-  Optional<String> getValue(String key);
-
-  void reload(Props copy);
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsImpl.java
deleted file mode 100644 (file)
index 02828a7..0000000
+++ /dev/null
@@ -1,47 +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.application.config;
-
-import java.util.Optional;
-import org.sonar.process.Props;
-
-public class AppSettingsImpl implements AppSettings {
-
-  private Props props;
-
-  AppSettingsImpl(Props props) {
-    this.props = props;
-  }
-
-  @Override
-  public Props getProps() {
-    return props;
-  }
-
-  @Override
-  public Optional<String> getValue(String key) {
-    return Optional.ofNullable(props.value(key));
-  }
-
-  @Override
-  public void reload(Props copy) {
-    this.props = copy;
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoader.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoader.java
deleted file mode 100644 (file)
index 5362c05..0000000
+++ /dev/null
@@ -1,26 +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.application.config;
-
-public interface AppSettingsLoader {
-
-  AppSettings load();
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java
deleted file mode 100644 (file)
index 50436b6..0000000
+++ /dev/null
@@ -1,102 +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.application.config;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.function.Consumer;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ConfigurationUtils;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class AppSettingsLoaderImpl implements AppSettingsLoader {
-
-  private final File homeDir;
-  private final String[] cliArguments;
-  private final Consumer<Props>[] consumers;
-
-  public AppSettingsLoaderImpl(String[] cliArguments) {
-    this(cliArguments, detectHomeDir(), new FileSystemSettings(), new JdbcSettings(), new ClusterSettings());
-  }
-
-  AppSettingsLoaderImpl(String[] cliArguments, File homeDir, Consumer<Props>... consumers) {
-    this.cliArguments = cliArguments;
-    this.homeDir = homeDir;
-    this.consumers = consumers;
-  }
-
-  File getHomeDir() {
-    return homeDir;
-  }
-
-  @Override
-  public AppSettings load() {
-    Properties p = loadPropertiesFile(homeDir);
-    p.putAll(CommandLineParser.parseArguments(cliArguments));
-    p.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
-    p = ConfigurationUtils.interpolateVariables(p, System.getenv());
-
-    // the difference between Properties and Props is that the latter
-    // supports decryption of values, so it must be used when values
-    // are accessed
-    Props props = new Props(p);
-    ProcessProperties.completeDefaults(props);
-    Arrays.stream(consumers).forEach(c -> c.accept(props));
-
-    return new AppSettingsImpl(props);
-  }
-
-  private static File detectHomeDir() {
-    try {
-      File appJar = new File(AppSettingsLoaderImpl.class.getProtectionDomain().getCodeSource().getLocation().toURI());
-      return appJar.getParentFile().getParentFile();
-    } catch (URISyntaxException e) {
-      throw new IllegalStateException("Cannot detect path of main jar file", e);
-    }
-  }
-
-  /**
-   * Loads the configuration file ${homeDir}/conf/sonar.properties.
-   * An empty {@link Properties} is returned if the file does not exist.
-   */
-  private static Properties loadPropertiesFile(File homeDir) {
-    Properties p = new Properties();
-    File propsFile = new File(homeDir, "conf/sonar.properties");
-    if (propsFile.exists()) {
-      try (Reader reader = new InputStreamReader(new FileInputStream(propsFile), UTF_8)) {
-        p.load(reader);
-      } catch (IOException e) {
-        throw new IllegalStateException("Cannot open file " + propsFile, e);
-      }
-    } else {
-      LoggerFactory.getLogger(AppSettingsLoaderImpl.class).warn("Configuration file not found: {}", propsFile);
-    }
-    return p;
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/ClusterSettings.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/ClusterSettings.java
deleted file mode 100644 (file)
index b6b8ce3..0000000
+++ /dev/null
@@ -1,185 +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.application.config;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.net.HostAndPort;
-import com.google.common.net.InetAddresses;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Consumer;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.process.MessageException;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-
-import static com.google.common.net.InetAddresses.forString;
-import static java.lang.String.format;
-import static java.util.Arrays.stream;
-import static org.apache.commons.lang.StringUtils.isBlank;
-import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
-import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
-import static org.sonar.process.ProcessProperties.CLUSTER_NETWORK_INTERFACES;
-import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
-import static org.sonar.process.ProcessProperties.CLUSTER_WEB_LEADER;
-import static org.sonar.process.ProcessProperties.JDBC_URL;
-import static org.sonar.process.ProcessProperties.SEARCH_HOST;
-
-public class ClusterSettings implements Consumer<Props> {
-
-  @Override
-  public void accept(Props props) {
-    if (!isClusterEnabled(props)) {
-      return;
-    }
-
-    checkProperties(props);
-  }
-
-  private static void checkProperties(Props props) {
-    // Cluster web leader is not allowed
-    if (props.value(CLUSTER_WEB_LEADER) != null) {
-      throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_LEADER));
-    }
-
-    if (props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED) &&
-      !props.valueAsBoolean(ProcessProperties.CLUSTER_SEARCH_DISABLED, false)
-      ) {
-      ensureMandatoryProperty(props, SEARCH_HOST);
-      ensureNotLoopback(props, SEARCH_HOST);
-    }
-    // Mandatory properties
-    ensureMandatoryProperty(props, CLUSTER_HOSTS);
-    ensureMandatoryProperty(props, CLUSTER_SEARCH_HOSTS);
-
-    // H2 Database is not allowed in cluster mode
-    String jdbcUrl = props.value(JDBC_URL);
-    if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) {
-      throw new MessageException("Embedded database is not supported in cluster mode");
-    }
-
-    // Loopback interfaces are forbidden for SEARCH_HOST and CLUSTER_NETWORK_INTERFACES
-    ensureNotLoopback(props, CLUSTER_HOSTS);
-    ensureNotLoopback(props, CLUSTER_NETWORK_INTERFACES);
-    ensureNotLoopback(props, CLUSTER_SEARCH_HOSTS);
-
-    ensureLocalAddress(props, SEARCH_HOST);
-    ensureLocalAddress(props, CLUSTER_NETWORK_INTERFACES);
-  }
-
-  private static void ensureMandatoryProperty(Props props, String key) {
-    if (isBlank(props.value(key))) {
-      throw new MessageException(format("Property [%s] is mandatory", key));
-    }
-  }
-
-  @VisibleForTesting
-  protected static void ensureNotLoopback(Props props, String key) {
-    String ipList = props.value(key);
-    if (ipList == null) {
-      return;
-    }
-
-    stream(ipList.split(","))
-      .filter(StringUtils::isNotBlank)
-      .map(StringUtils::trim)
-      .forEach(ip -> {
-        InetAddress inetAddress = convertToInetAddress(ip, key);
-        if (inetAddress.isLoopbackAddress()) {
-          throw new MessageException(format("The interface address [%s] of [%s] must not be a loopback address", ip, key));
-        }
-      });
-  }
-
-  private static void ensureLocalAddress(Props props, String key) {
-    String ipList = props.value(key);
-
-    if (ipList == null) {
-      return;
-    }
-
-    stream(ipList.split(","))
-      .filter(StringUtils::isNotBlank)
-      .map(StringUtils::trim)
-      .forEach(ip -> {
-        InetAddress inetAddress = convertToInetAddress(ip, key);
-        try {
-          if (NetworkInterface.getByInetAddress(inetAddress) == null) {
-            throw new MessageException(format("The interface address [%s] of [%s] is not a local address", ip, key));
-          }
-        } catch (SocketException e) {
-          throw new MessageException(format("The interface address [%s] of [%s] is not a local address", ip, key));
-        }
-      });
-  }
-
-  private static InetAddress convertToInetAddress(String text, String key) {
-    InetAddress inetAddress;
-    HostAndPort hostAndPort = HostAndPort.fromString(text);
-    if (!InetAddresses.isInetAddress(hostAndPort.getHostText())) {
-      try {
-        inetAddress =InetAddress.getByName(hostAndPort.getHostText());
-      } catch (UnknownHostException e) {
-        throw new MessageException(format("The interface address [%s] of [%s] cannot be resolved : %s", text, key, e.getMessage()));
-      }
-    } else {
-      inetAddress = forString(hostAndPort.getHostText());
-    }
-
-    return inetAddress;
-  }
-
-  public static boolean isClusterEnabled(AppSettings settings) {
-    return isClusterEnabled(settings.getProps());
-  }
-
-  private static boolean isClusterEnabled(Props props) {
-    return props.valueAsBoolean(CLUSTER_ENABLED);
-  }
-
-  public static List<ProcessId> getEnabledProcesses(AppSettings settings) {
-    if (!isClusterEnabled(settings)) {
-      return Arrays.asList(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
-    }
-    List<ProcessId> enabled = new ArrayList<>();
-    if (!settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_SEARCH_DISABLED)) {
-      enabled.add(ProcessId.ELASTICSEARCH);
-    }
-    if (!settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_WEB_DISABLED)) {
-      enabled.add(ProcessId.WEB_SERVER);
-    }
-
-    if (!settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_CE_DISABLED)) {
-      enabled.add(ProcessId.COMPUTE_ENGINE);
-    }
-    return enabled;
-  }
-
-  public static boolean isLocalElasticsearchEnabled(AppSettings settings) {
-    return !isClusterEnabled(settings.getProps()) ||
-      !settings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_SEARCH_DISABLED);
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/CommandLineParser.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/CommandLineParser.java
deleted file mode 100644 (file)
index 14422fb..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.application.config;
-
-import org.apache.commons.lang.StringUtils;
-
-import java.util.Map;
-import java.util.Properties;
-
-public class CommandLineParser {
-
-  private CommandLineParser() {
-    // prevent instantiation
-  }
-
-  /**
-   * Build properties from command-line arguments and system properties
-   */
-  public static Properties parseArguments(String[] args) {
-    Properties props = argumentsToProperties(args);
-
-    // complete with only the system properties that start with "sonar."
-    for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
-      String key = entry.getKey().toString();
-      if (key.startsWith("sonar.")) {
-        props.setProperty(key, entry.getValue().toString());
-      }
-    }
-    return props;
-  }
-
-  /**
-   * Convert strings "-Dkey=value" to properties
-   */
-  static Properties argumentsToProperties(String[] args) {
-    Properties props = new Properties();
-    for (String arg : args) {
-      if (!arg.startsWith("-D") || !arg.contains("=")) {
-        throw new IllegalArgumentException(String.format(
-          "Command-line argument must start with -D, for example -Dsonar.jdbc.username=sonar. Got: %s", arg));
-      }
-      String key = StringUtils.substringBefore(arg, "=").substring(2);
-      String value = StringUtils.substringAfter(arg, "=");
-      props.setProperty(key, value);
-    }
-    return props;
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/FileSystemSettings.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/FileSystemSettings.java
deleted file mode 100644 (file)
index b7953ac..0000000
+++ /dev/null
@@ -1,59 +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.application.config;
-
-import java.io.File;
-import java.util.function.Consumer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.Props;
-
-import static org.sonar.process.ProcessProperties.PATH_DATA;
-import static org.sonar.process.ProcessProperties.PATH_HOME;
-import static org.sonar.process.ProcessProperties.PATH_LOGS;
-import static org.sonar.process.ProcessProperties.PATH_TEMP;
-import static org.sonar.process.ProcessProperties.PATH_WEB;
-
-public class FileSystemSettings implements Consumer<Props> {
-
-  private static final Logger LOG = LoggerFactory.getLogger(FileSystemSettings.class);
-
-  @Override
-  public void accept(Props props) {
-    ensurePropertyIsAbsolutePath(props, PATH_DATA);
-    ensurePropertyIsAbsolutePath(props, PATH_WEB);
-    ensurePropertyIsAbsolutePath(props, PATH_LOGS);
-    ensurePropertyIsAbsolutePath(props, PATH_TEMP);
-  }
-
-  private static File ensurePropertyIsAbsolutePath(Props props, String propKey) {
-    File homeDir = props.nonNullValueAsFile(PATH_HOME);
-    // default values are set by ProcessProperties
-    String path = props.nonNullValue(propKey);
-    File d = new File(path);
-    if (!d.isAbsolute()) {
-      d = new File(homeDir, path);
-      LOG.trace("Overriding property {} from relative path '{}' to absolute path '{}'", path, d.getAbsolutePath());
-      props.set(propKey, d.getAbsolutePath());
-    }
-    return d;
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/JdbcSettings.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/JdbcSettings.java
deleted file mode 100644 (file)
index 9011f16..0000000
+++ /dev/null
@@ -1,167 +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.application.config;
-
-import java.io.File;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.MessageException;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-
-import static java.lang.String.format;
-import static org.apache.commons.lang.StringUtils.isEmpty;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.sonar.process.ProcessProperties.JDBC_EMBEDDED_PORT;
-import static org.sonar.process.ProcessProperties.JDBC_URL;
-
-public class JdbcSettings implements Consumer<Props> {
-
-  private static final int JDBC_EMBEDDED_PORT_DEFAULT_VALUE = 9092;
-
-  enum Provider {
-    H2("lib/jdbc/h2"), SQLSERVER("lib/jdbc/mssql"), MYSQL("lib/jdbc/mysql"), ORACLE("extensions/jdbc-driver/oracle"),
-    POSTGRESQL("lib/jdbc/postgresql");
-
-    final String path;
-
-    Provider(String path) {
-      this.path = path;
-    }
-  }
-
-  @Override
-  public void accept(Props props) {
-    File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
-    Provider provider = resolveProviderAndEnforceNonnullJdbcUrl(props);
-    String url = props.value(JDBC_URL);
-    checkUrlParameters(provider, url);
-    String driverPath = driverPath(homeDir, provider);
-    props.set(ProcessProperties.JDBC_DRIVER_PATH, driverPath);
-  }
-
-  String driverPath(File homeDir, Provider provider) {
-    String dirPath = provider.path;
-    File dir = new File(homeDir, dirPath);
-    if (!dir.exists()) {
-      throw new MessageException("Directory does not exist: " + dirPath);
-    }
-    List<File> files = new ArrayList<>(FileUtils.listFiles(dir, new String[] {"jar"}, false));
-    if (files.isEmpty()) {
-      throw new MessageException("Directory does not contain JDBC driver: " + dirPath);
-    }
-    if (files.size() > 1) {
-      throw new MessageException("Directory must contain only one JAR file: " + dirPath);
-    }
-    return files.get(0).getAbsolutePath();
-  }
-
-  Provider resolveProviderAndEnforceNonnullJdbcUrl(Props props) {
-    String url = props.value(JDBC_URL);
-    Integer embeddedDatabasePort = props.valueAsInt(JDBC_EMBEDDED_PORT);
-
-    if (embeddedDatabasePort != null) {
-      String correctUrl = buildH2JdbcUrl(embeddedDatabasePort);
-      warnIfUrlIsSet(embeddedDatabasePort, url, correctUrl);
-      props.set(JDBC_URL, correctUrl);
-      return Provider.H2;
-    }
-
-    if (isEmpty(url)) {
-      props.set(JDBC_URL, buildH2JdbcUrl(JDBC_EMBEDDED_PORT_DEFAULT_VALUE));
-      props.set(JDBC_EMBEDDED_PORT, String.valueOf(JDBC_EMBEDDED_PORT_DEFAULT_VALUE));
-      return Provider.H2;
-    }
-
-    Pattern pattern = Pattern.compile("jdbc:(\\w+):.+");
-    Matcher matcher = pattern.matcher(url);
-    if (!matcher.find()) {
-      throw new MessageException(format("Bad format of JDBC URL: %s", url));
-    }
-    String key = matcher.group(1);
-    try {
-      return Provider.valueOf(StringUtils.upperCase(key));
-    } catch (IllegalArgumentException e) {
-      throw new MessageException(format("Unsupported JDBC driver provider: %s", key));
-    }
-  }
-
-  private static String buildH2JdbcUrl(int embeddedDatabasePort) {
-    InetAddress ip = InetAddress.getLoopbackAddress();
-    String host;
-    if (ip instanceof Inet6Address) {
-      host = "[" + ip.getHostAddress() + "]";
-    } else {
-      host = ip.getHostAddress();
-    }
-    return format("jdbc:h2:tcp://%s:%d/sonar", host, embeddedDatabasePort);
-  }
-
-  void checkUrlParameters(Provider provider, String url) {
-    if (Provider.MYSQL.equals(provider)) {
-      checkRequiredParameter(url, "useUnicode=true");
-      checkRequiredParameter(url, "characterEncoding=utf8");
-      checkRecommendedParameter(url, "rewriteBatchedStatements=true");
-      checkRecommendedParameter(url, "useConfigs=maxPerformance");
-    }
-  }
-
-  private static void warnIfUrlIsSet(int port, String existing, String expectedUrl) {
-    if (isNotEmpty(existing)) {
-      Logger logger = LoggerFactory.getLogger(JdbcSettings.class);
-      if (expectedUrl.equals(existing)) {
-        logger.warn("To change H2 database port, only property '{}' should be set (which current value is '{}'). " +
-          "Remove property '{}' from configuration to remove this warning.",
-          JDBC_EMBEDDED_PORT, port,
-          JDBC_URL);
-      } else {
-        logger.warn("Both '{}' and '{}' properties are set. " +
-          "The value of property '{}' ('{}') is not consistent with the value of property '{}' ('{}'). " +
-          "The value of property '{}' will be ignored and value '{}' will be used instead. " +
-          "To remove this warning, either remove property '{}' if your intent was to use the embedded H2 database, otherwise remove property '{}'.",
-          JDBC_EMBEDDED_PORT, JDBC_URL,
-          JDBC_URL, existing, JDBC_EMBEDDED_PORT, port,
-          JDBC_URL, expectedUrl,
-          JDBC_URL, JDBC_EMBEDDED_PORT);
-      }
-    }
-  }
-
-  private static void checkRequiredParameter(String url, String val) {
-    if (!url.contains(val)) {
-      throw new MessageException(format("JDBC URL must have the property '%s'", val));
-    }
-  }
-
-  private void checkRecommendedParameter(String url, String val) {
-    if (!url.contains(val)) {
-      LoggerFactory.getLogger(getClass()).warn("JDBC URL is recommended to have the property '{}'", val);
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java
deleted file mode 100644 (file)
index 11b23e9..0000000
+++ /dev/null
@@ -1,59 +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.application.config;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-
-import static java.lang.String.format;
-
-public class SonarQubeVersionHelper {
-  private static final String SONARQUBE_VERSION_PATH = "/sonarqube-version.txt";
-
-  private static String sonarqubeVersion;
-
-  private SonarQubeVersionHelper() {
-    // only static methods
-  }
-
-  public static String getSonarqubeVersion() {
-    if (sonarqubeVersion == null) {
-      loadVersion();
-    }
-    return sonarqubeVersion;
-  }
-
-  private static synchronized void loadVersion() {
-    try {
-      try (BufferedReader in = new BufferedReader(
-        new InputStreamReader(
-          SonarQubeVersionHelper.class.getResourceAsStream(SONARQUBE_VERSION_PATH),
-          StandardCharsets.UTF_8
-        ))) {
-        sonarqubeVersion = in.readLine();
-      }
-    } catch (IOException e) {
-      throw new IllegalStateException(format("Cannot load %s from classpath", SONARQUBE_VERSION_PATH), e);
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/package-info.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/package-info.java
deleted file mode 100644 (file)
index 192ba67..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.application.config;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/package-info.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/package-info.java
deleted file mode 100644 (file)
index 7dc3ab2..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.application;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/AbstractProcessMonitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/AbstractProcessMonitor.java
deleted file mode 100644 (file)
index 1583a72..0000000
+++ /dev/null
@@ -1,88 +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.application.process;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.TimeUnit;
-import javax.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ProcessId;
-
-abstract class AbstractProcessMonitor implements ProcessMonitor {
-
-  private static final Logger LOG = LoggerFactory.getLogger(AbstractProcessMonitor.class);
-  private static final int EXPECTED_EXIT_VALUE = 0;
-
-  protected final Process process;
-  private final ProcessId processId;
-
-  protected AbstractProcessMonitor(Process process, ProcessId processId) {
-    this.process = process;
-    this.processId = processId;
-  }
-
-  public InputStream getInputStream() {
-    return process.getInputStream();
-  }
-
-  public InputStream getErrorStream() {
-    return process.getErrorStream();
-  }
-
-  public void closeStreams() {
-    closeQuietly(process.getInputStream());
-    closeQuietly(process.getOutputStream());
-    closeQuietly(process.getErrorStream());
-  }
-
-  private static void closeQuietly(@Nullable Closeable closeable) {
-    try {
-      if (closeable != null) {
-        closeable.close();
-      }
-    } catch (IOException ignored) {
-      // ignore
-    }
-  }
-
-  public boolean isAlive() {
-    return process.isAlive();
-  }
-
-  public void destroyForcibly() {
-    process.destroyForcibly();
-  }
-
-  public void waitFor() throws InterruptedException {
-    int exitValue = process.waitFor();
-    if (exitValue != EXPECTED_EXIT_VALUE) {
-      LOG.warn("Process exited with exit value [{}]: {}", processId.getKey(), exitValue);
-    } else {
-      LOG.debug("Process exited with exit value [{}]: {}", processId.getKey(), exitValue);
-    }
-  }
-
-  public void waitFor(long timeout, TimeUnit unit) throws InterruptedException {
-    process.waitFor(timeout, unit);
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsProcessMonitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsProcessMonitor.java
deleted file mode 100644 (file)
index 52c0a9d..0000000
+++ /dev/null
@@ -1,213 +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.application.process;
-
-import com.google.common.net.HostAndPort;
-import io.netty.util.ThreadDeathWatcher;
-import io.netty.util.concurrent.GlobalEventExecutor;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
-import org.elasticsearch.client.transport.NoNodeAvailableException;
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.elasticsearch.common.network.NetworkModule;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.transport.InetSocketTransportAddress;
-import org.elasticsearch.common.transport.TransportAddress;
-import org.elasticsearch.transport.Netty4Plugin;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ProcessId;
-import org.sonar.process.command.EsCommand;
-
-import static java.util.Collections.singletonList;
-import static java.util.Collections.unmodifiableList;
-import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
-import static org.sonar.application.process.EsProcessMonitor.Status.CONNECTION_REFUSED;
-import static org.sonar.application.process.EsProcessMonitor.Status.GREEN;
-import static org.sonar.application.process.EsProcessMonitor.Status.KO;
-import static org.sonar.application.process.EsProcessMonitor.Status.RED;
-import static org.sonar.application.process.EsProcessMonitor.Status.YELLOW;
-
-public class EsProcessMonitor extends AbstractProcessMonitor {
-  private static final Logger LOG = LoggerFactory.getLogger(EsProcessMonitor.class);
-  private static final int WAIT_FOR_UP_DELAY_IN_MILLIS = 100;
-  private static final int WAIT_FOR_UP_TIMEOUT = 10 * 60; /* 1min */
-
-  private final AtomicBoolean nodeUp = new AtomicBoolean(false);
-  private final AtomicBoolean nodeOperational = new AtomicBoolean(false);
-  private final EsCommand esCommand;
-  private AtomicReference<TransportClient> transportClient = new AtomicReference<>(null);
-
-  public EsProcessMonitor(Process process, ProcessId processId, EsCommand esCommand) {
-    super(process, processId);
-    this.esCommand = esCommand;
-  }
-
-  @Override
-  public boolean isOperational() {
-    if (nodeOperational.get()) {
-      return true;
-    }
-
-    boolean flag = false;
-    try {
-      flag = checkOperational();
-    } catch (InterruptedException e) {
-      LOG.trace("Interrupted while checking ES node is operational", e);
-      Thread.currentThread().interrupt();
-    } finally {
-      if (flag) {
-        transportClient.set(null);
-        nodeOperational.set(true);
-      }
-    }
-    return nodeOperational.get();
-  }
-
-  private boolean checkOperational() throws InterruptedException {
-    int i = 0;
-    Status status = checkStatus();
-    do {
-      if (status != Status.CONNECTION_REFUSED) {
-        nodeUp.set(true);
-      } else {
-        Thread.sleep(WAIT_FOR_UP_DELAY_IN_MILLIS);
-        i++;
-        status = checkStatus();
-      }
-    } while (!nodeUp.get() && i < WAIT_FOR_UP_TIMEOUT);
-    return status == YELLOW || status == GREEN;
-  }
-
-  static class MinimalTransportClient extends TransportClient {
-
-    MinimalTransportClient(Settings settings) {
-      super(settings, unmodifiableList(singletonList(Netty4Plugin.class)));
-    }
-
-    @Override
-    public void close() {
-      super.close();
-      if (NetworkModule.TRANSPORT_TYPE_SETTING.exists(settings) == false
-          || NetworkModule.TRANSPORT_TYPE_SETTING.get(settings).equals(Netty4Plugin.NETTY_TRANSPORT_NAME)) {
-        try {
-          GlobalEventExecutor.INSTANCE.awaitInactivity(5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-          Thread.currentThread().interrupt();
-        }
-        try {
-          ThreadDeathWatcher.awaitInactivity(5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-          Thread.currentThread().interrupt();
-        }
-      }
-    }
-
-  }
-
-  private Status checkStatus() {
-    try {
-      ClusterHealthResponse response = getTransportClient().admin().cluster()
-        .health(new ClusterHealthRequest().waitForStatus(ClusterHealthStatus.YELLOW).timeout(timeValueSeconds(30)))
-        .actionGet();
-      if (response.getStatus() == ClusterHealthStatus.GREEN) {
-        return GREEN;
-      }
-      if (response.getStatus() == ClusterHealthStatus.YELLOW) {
-        return YELLOW;
-      }
-      if (response.getStatus() == ClusterHealthStatus.RED) {
-        return RED;
-      }
-      return KO;
-    } catch (NoNodeAvailableException e) {
-      return CONNECTION_REFUSED;
-    } catch (Exception e) {
-      LOG.error("Failed to check status", e);
-      return KO;
-    }
-  }
-
-  private TransportClient getTransportClient() {
-    TransportClient res = this.transportClient.get();
-    if (res == null) {
-      res = buildTransportClient();
-      if (this.transportClient.compareAndSet(null, res)) {
-        return res;
-      }
-      return this.transportClient.get();
-    }
-    return res;
-  }
-
-  private TransportClient buildTransportClient() {
-    Settings.Builder esSettings = Settings.builder();
-
-    // mandatory property defined by bootstrap process
-    esSettings.put("cluster.name", esCommand.getClusterName());
-
-    TransportClient nativeClient = new MinimalTransportClient(esSettings.build());
-    HostAndPort host = HostAndPort.fromParts(esCommand.getHost(), esCommand.getPort());
-    addHostToClient(host, nativeClient);
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Connected to Elasticsearch node: [{}]", displayedAddresses(nativeClient));
-    }
-    return nativeClient;
-  }
-
-  private static void addHostToClient(HostAndPort host, TransportClient client) {
-    try {
-      client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host.getHostText()), host.getPortOrDefault(9001)));
-    } catch (UnknownHostException e) {
-      throw new IllegalStateException("Can not resolve host [" + host + "]", e);
-    }
-  }
-
-  private static String displayedAddresses(TransportClient nativeClient) {
-    return nativeClient.transportAddresses().stream().map(TransportAddress::toString).collect(Collectors.joining(", "));
-  }
-
-  enum Status {
-    CONNECTION_REFUSED, KO, RED, YELLOW, GREEN
-  }
-
-  @Override
-  public void askForStop() {
-    process.destroy();
-  }
-
-  @Override
-  public boolean askedForRestart() {
-    // ES does not support asking for restart
-    return false;
-  }
-
-  @Override
-  public void acknowledgeAskForRestart() {
-    // nothing to do
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/Lifecycle.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/Lifecycle.java
deleted file mode 100644 (file)
index 4d185fa..0000000
+++ /dev/null
@@ -1,97 +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.application.process;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ProcessId;
-
-import static org.sonar.application.process.Lifecycle.State.INIT;
-import static org.sonar.application.process.Lifecycle.State.STARTED;
-import static org.sonar.application.process.Lifecycle.State.STARTING;
-import static org.sonar.application.process.Lifecycle.State.STOPPED;
-import static org.sonar.application.process.Lifecycle.State.STOPPING;
-
-public class Lifecycle {
-
-  public enum State {
-    INIT, STARTING, STARTED, STOPPING, STOPPED
-  }
-
-  private static final Logger LOG = LoggerFactory.getLogger(Lifecycle.class);
-  private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
-
-  private final ProcessId processId;
-  private final List<ProcessLifecycleListener> listeners;
-  private State state;
-
-  public Lifecycle(ProcessId processId, List<ProcessLifecycleListener> listeners) {
-    this(processId, listeners, INIT);
-  }
-
-  Lifecycle(ProcessId processId, List<ProcessLifecycleListener> listeners, State initialState) {
-    this.processId = processId;
-    this.listeners = listeners;
-    this.state = initialState;
-  }
-
-  private static Map<State, Set<State>> buildTransitions() {
-    Map<State, Set<State>> res = new EnumMap<>(State.class);
-    res.put(INIT, toSet(STARTING));
-    res.put(STARTING, toSet(STARTED, STOPPING, STOPPED));
-    res.put(STARTED, toSet(STOPPING, STOPPED));
-    res.put(STOPPING, toSet(STOPPED));
-    res.put(STOPPED, toSet());
-    return res;
-  }
-
-  private static Set<State> toSet(State... states) {
-    if (states.length == 0) {
-      return Collections.emptySet();
-    }
-    if (states.length == 1) {
-      return Collections.singleton(states[0]);
-    }
-    return EnumSet.copyOf(Arrays.asList(states));
-  }
-
-  State getState() {
-    return state;
-  }
-
-  synchronized boolean tryToMoveTo(State to) {
-    boolean res = false;
-    State currentState = state;
-    if (TRANSITIONS.get(currentState).contains(to)) {
-      this.state = to;
-      res = true;
-      listeners.forEach(listener -> listener.onProcessState(processId, to));
-    }
-    LOG.trace("tryToMoveTo {} from {} to {} => {}", processId.getKey(), currentState, to, res);
-    return res;
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java
deleted file mode 100644 (file)
index a54f53f..0000000
+++ /dev/null
@@ -1,69 +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.application.process;
-
-import org.sonar.process.ProcessId;
-import org.sonar.process.sharedmemoryfile.ProcessCommands;
-
-import static java.util.Objects.requireNonNull;
-
-class ProcessCommandsProcessMonitor extends AbstractProcessMonitor {
-
-  private final ProcessCommands commands;
-
-  ProcessCommandsProcessMonitor(Process process, ProcessId processId, ProcessCommands commands) {
-    super(process, processId);
-    this.commands = requireNonNull(commands, "commands can't be null");
-  }
-
-  /**
-   * Whether the process has set the operational flag (in ipc shared memory)
-   */
-  @Override
-  public boolean isOperational() {
-    return commands.isOperational();
-  }
-
-  /**
-   * Send request to gracefully stop to the process (via ipc shared memory)
-   */
-  @Override
-  public void askForStop() {
-    commands.askForStop();
-  }
-
-  /**
-   * Whether the process asked for a full restart (via ipc shared memory)
-   */
-  @Override
-  public boolean askedForRestart() {
-    return commands.askedForRestart();
-  }
-
-  /**
-   * Removes the flag in ipc shared memory so that next call to {@link #askedForRestart()}
-   * returns {@code false}, except if meanwhile process asks again for restart.
-   */
-  @Override
-  public void acknowledgeAskForRestart() {
-    commands.acknowledgeAskForRestart();
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessEventListener.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessEventListener.java
deleted file mode 100644 (file)
index a7f6a7b..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.application.process;
-
-import org.sonar.process.ProcessId;
-
-@FunctionalInterface
-public interface ProcessEventListener {
-
-  enum Type {
-    OPERATIONAL,
-    ASK_FOR_RESTART
-  }
-
-  /**
-   * This method is called when the process with the specified {@link ProcessId}
-   * sends the event through the ipc shared memory.
-   * Note that there can be a delay since the instant the process sets the flag
-   * (see {@link SQProcess#WATCHER_DELAY_MS}).
-   *
-   * Call blocks the process watcher. Implementations should be asynchronous and
-   * fork a new thread if call can be long.
-   */
-  void onProcessEvent(ProcessId processId, Type type);
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncher.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncher.java
deleted file mode 100644 (file)
index c39f91b..0000000
+++ /dev/null
@@ -1,44 +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.application.process;
-
-import java.io.Closeable;
-import org.sonar.process.command.EsCommand;
-import org.sonar.process.command.JavaCommand;
-
-public interface ProcessLauncher extends Closeable {
-
-  @Override
-  void close();
-
-  /**
-   * Launch an ES command.
-   *
-   * @throws IllegalStateException if an error occurs
-   */
-  ProcessMonitor launch(EsCommand esCommand);
-
-  /**
-   * Launch a Java command.
-   * 
-   * @throws IllegalStateException if an error occurs
-   */
-  ProcessMonitor launch(JavaCommand javaCommand);
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
deleted file mode 100644 (file)
index 549e4dd..0000000
+++ /dev/null
@@ -1,267 +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.application.process;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-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 org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-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;
-
-import static java.lang.String.format;
-import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
-import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
-import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
-import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;
-
-public class ProcessLauncherImpl implements ProcessLauncher {
-  private static final Logger LOG = LoggerFactory.getLogger(ProcessLauncherImpl.class);
-
-  private final File tempDir;
-  private final AllProcessesCommands allProcessesCommands;
-  private final Supplier<ProcessBuilder> processBuilderSupplier;
-
-  public ProcessLauncherImpl(File tempDir) {
-    this(tempDir, new AllProcessesCommands(tempDir), JavaLangProcessBuilder::new);
-  }
-
-  ProcessLauncherImpl(File tempDir, AllProcessesCommands allProcessesCommands, Supplier<ProcessBuilder> processBuilderSupplier) {
-    this.tempDir = tempDir;
-    this.allProcessesCommands = allProcessesCommands;
-    this.processBuilderSupplier = processBuilderSupplier;
-  }
-
-  @Override
-  public void close() {
-    allProcessesCommands.close();
-  }
-
-  @Override
-  public ProcessMonitor launch(EsCommand esCommand) {
-    Process process = null;
-    ProcessId processId = esCommand.getProcessId();
-    try {
-      writeConfFiles(esCommand);
-      ProcessBuilder processBuilder = create(esCommand);
-      logLaunchedCommand(esCommand, processBuilder);
-
-      process = processBuilder.start();
-
-      return new EsProcessMonitor(process, processId, esCommand);
-    } catch (Exception e) {
-      // just in case
-      if (process != null) {
-        process.destroyForcibly();
-      }
-      throw new IllegalStateException(format("Fail to launch process [%s]", processId.getKey()), e);
-    }
-  }
-
-  private void writeConfFiles(EsCommand esCommand) {
-    File confDir = esCommand.getConfDir();
-    if (!confDir.exists() && !confDir.mkdirs()) {
-      String error = format("Failed to create temporary configuration directory [%s]", confDir.getAbsolutePath());
-      LOG.error(error);
-      throw new IllegalStateException(error);
-    }
-
-    try {
-      IOUtils.copy(getClass().getResourceAsStream("elasticsearch.yml"), new FileOutputStream(new File(confDir, "elasticsearch.yml")));
-      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);
-    }
-  }
-
-  @Override
-  public ProcessMonitor launch(JavaCommand javaCommand) {
-    Process process = null;
-    ProcessId processId = javaCommand.getProcessId();
-    try {
-      ProcessCommands commands = allProcessesCommands.createAfterClean(processId.getIpcIndex());
-
-      ProcessBuilder processBuilder = create(javaCommand);
-      logLaunchedCommand(javaCommand, processBuilder);
-      process = processBuilder.start();
-      return new ProcessCommandsProcessMonitor(process, processId, commands);
-    } catch (Exception e) {
-      // just in case
-      if (process != null) {
-        process.destroyForcibly();
-      }
-      throw new IllegalStateException(format("Fail to launch process [%s]", processId.getKey()), e);
-    }
-  }
-
-  private static <T extends AbstractCommand> void logLaunchedCommand(AbstractCommand<T> command, ProcessBuilder processBuilder) {
-    if (LOG.isInfoEnabled()) {
-      LOG.info("Launch process[{}] from [{}]: {}",
-        command.getProcessId(),
-        command.getWorkDir().getAbsolutePath(),
-        String.join(" ", processBuilder.command()));
-    }
-  }
-
-  private ProcessBuilder create(EsCommand esCommand) {
-    List<String> commands = new ArrayList<>();
-    commands.add(esCommand.getExecutable().getAbsolutePath());
-    commands.addAll(esCommand.getEsOptions());
-
-    return create(esCommand, commands);
-  }
-
-  private <T extends JvmOptions> ProcessBuilder create(JavaCommand<T> javaCommand) {
-    List<String> commands = new ArrayList<>();
-    commands.add(buildJavaPath());
-    commands.addAll(javaCommand.getJvmOptions().getAll());
-    commands.addAll(buildClasspath(javaCommand));
-    commands.add(javaCommand.getClassName());
-    commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
-
-    return create(javaCommand, commands);
-  }
-
-  private ProcessBuilder create(AbstractCommand<?> javaCommand, List<String> commands) {
-    ProcessBuilder processBuilder = processBuilderSupplier.get();
-    processBuilder.command(commands);
-    processBuilder.directory(javaCommand.getWorkDir());
-    processBuilder.environment().putAll(javaCommand.getEnvVariables());
-    processBuilder.redirectErrorStream(true);
-    return processBuilder;
-  }
-
-  private static String buildJavaPath() {
-    String separator = System.getProperty("file.separator");
-    return new File(new File(System.getProperty("java.home")), "bin" + separator + "java").getAbsolutePath();
-  }
-
-  private static List<String> buildClasspath(JavaCommand javaCommand) {
-    String pathSeparator = System.getProperty("path.separator");
-    return Arrays.asList("-cp", String.join(pathSeparator, javaCommand.getClasspath()));
-  }
-
-  private File buildPropertiesFile(JavaCommand javaCommand) {
-    File propertiesFile = null;
-    try {
-      propertiesFile = File.createTempFile("sq-process", "properties", tempDir);
-      Properties props = new Properties();
-      props.putAll(javaCommand.getArguments());
-      props.setProperty(PROPERTY_PROCESS_KEY, javaCommand.getProcessId().getKey());
-      props.setProperty(PROPERTY_PROCESS_INDEX, Integer.toString(javaCommand.getProcessId().getIpcIndex()));
-      props.setProperty(PROPERTY_TERMINATION_TIMEOUT, "60000");
-      props.setProperty(PROPERTY_SHARED_PATH, tempDir.getAbsolutePath());
-      try (OutputStream out = new FileOutputStream(propertiesFile)) {
-        props.store(out, format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
-      }
-      return propertiesFile;
-    } catch (Exception e) {
-      throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e);
-    }
-  }
-
-  /**
-   * An interface of the methods of {@link java.lang.ProcessBuilder} that we use in {@link ProcessLauncherImpl}.
-   * <p>Allows testing creating processes without actualling creating them at OS level</p>
-   */
-  public interface ProcessBuilder {
-    List<String> command();
-
-    ProcessBuilder command(List<String> commands);
-
-    ProcessBuilder directory(File dir);
-
-    Map<String, String> environment();
-
-    ProcessBuilder redirectErrorStream(boolean b);
-
-    Process start() throws IOException;
-  }
-
-  private static class JavaLangProcessBuilder implements ProcessBuilder {
-    private final java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder();
-
-    /**
-     * @see java.lang.ProcessBuilder#command()
-     */
-    @Override
-    public List<String> command() {
-      return builder.command();
-    }
-
-    /**
-     * @see java.lang.ProcessBuilder#command(List)
-     */
-    @Override
-    public ProcessBuilder command(List<String> commands) {
-      builder.command(commands);
-      return this;
-    }
-
-    /**
-     * @see java.lang.ProcessBuilder#directory(File)
-     */
-    @Override
-    public ProcessBuilder directory(File dir) {
-      builder.directory(dir);
-      return this;
-    }
-
-    /**
-     * @see java.lang.ProcessBuilder#environment()
-     */
-    @Override
-    public Map<String, String> environment() {
-      return builder.environment();
-    }
-
-    /**
-     * @see java.lang.ProcessBuilder#redirectErrorStream(boolean)
-     */
-    @Override
-    public ProcessBuilder redirectErrorStream(boolean b) {
-      builder.redirectErrorStream(b);
-      return this;
-    }
-
-    /**
-     * @see java.lang.ProcessBuilder#start()
-     */
-    @Override
-    public Process start() throws IOException {
-      return builder.start();
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java
deleted file mode 100644 (file)
index 384499b..0000000
+++ /dev/null
@@ -1,36 +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.application.process;
-
-import org.sonar.process.ProcessId;
-
-@FunctionalInterface
-public interface ProcessLifecycleListener {
-
-  /**
-   * This method is called when the state of the process with the specified {@link ProcessId}
-   * changes.
-   *
-   * Call blocks the process watcher. Implementations should be asynchronous and
-   * fork a new thread if call can be long.
-   */
-  void onProcessState(ProcessId processId, Lifecycle.State state);
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitor.java
deleted file mode 100644 (file)
index 9a88393..0000000
+++ /dev/null
@@ -1,88 +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.application.process;
-
-import java.io.InputStream;
-import java.util.concurrent.TimeUnit;
-
-public interface ProcessMonitor {
-
-  /**
-   * @see Process#getInputStream()
-   */
-  InputStream getInputStream();
-
-  /**
-   * @see Process#getErrorStream()
-   */
-  InputStream getErrorStream();
-
-  /**
-   * Closes the streams {@link Process#getInputStream()}, {@link Process#getOutputStream()}
-   * and {@link Process#getErrorStream()}.
-   *
-   * No exceptions are thrown in case of errors.
-   */
-  void closeStreams();
-
-  /**
-   * @see Process#isAlive()
-   */
-  boolean isAlive();
-
-  /**
-   * @see Process#destroyForcibly()
-   */
-  void destroyForcibly();
-
-  /**
-   * @see Process#waitFor()
-   */
-  void waitFor() throws InterruptedException;
-
-  /**
-   * @see Process#waitFor(long, TimeUnit)
-   */
-  void waitFor(long timeout, TimeUnit timeoutUnit) throws InterruptedException;
-
-  /**
-   * Whether the process has reach operational state after startup.
-   */
-  boolean isOperational();
-
-  /**
-   * Send request to gracefully stop to the process
-   */
-  void askForStop();
-
-  /**
-   * Whether the process asked for a full restart
-   */
-  boolean askedForRestart();
-
-  /**
-   * Sends a signal to the process to acknowledge that the parent process received the request to restart from the
-   * child process send via {@link #askedForRestart()}.
-   * <br/>
-   * Child process will typically stop sending the signal requesting restart from now on.
-   */
-  void acknowledgeAskForRestart();
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/SQProcess.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/SQProcess.java
deleted file mode 100644 (file)
index 6aa7a29..0000000
+++ /dev/null
@@ -1,269 +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.application.process;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.ProcessId;
-
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-public class SQProcess {
-
-  public static final long DEFAULT_WATCHER_DELAY_MS = 500L;
-  private static final Logger LOG = LoggerFactory.getLogger(SQProcess.class);
-
-  private final ProcessId processId;
-  private final Lifecycle lifecycle;
-  private final List<ProcessEventListener> eventListeners;
-  private final long watcherDelayMs;
-
-  private ProcessMonitor process;
-  private StreamGobbler stdOutGobbler;
-  private StreamGobbler stdErrGobbler;
-  private final StopWatcher stopWatcher;
-  private final EventWatcher eventWatcher;
-  // keep flag so that the operational event is sent only once
-  // to listeners
-  private final AtomicBoolean operational = new AtomicBoolean(false);
-
-  private SQProcess(Builder builder) {
-    this.processId = requireNonNull(builder.processId, "processId can't be null");
-    this.lifecycle = new Lifecycle(this.processId, builder.lifecycleListeners);
-    this.eventListeners = builder.eventListeners;
-    this.watcherDelayMs = builder.watcherDelayMs;
-    this.stopWatcher = new StopWatcher();
-    this.eventWatcher = new EventWatcher();
-  }
-
-  public boolean start(Supplier<ProcessMonitor> commandLauncher) {
-    if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
-      // has already been started
-      return false;
-    }
-    try {
-      this.process = commandLauncher.get();
-    } catch (RuntimeException e) {
-      LOG.error(format("Fail to launch process [%s]", processId.getKey()), e);
-      lifecycle.tryToMoveTo(Lifecycle.State.STOPPED);
-      throw e;
-    }
-    this.stdOutGobbler = new StreamGobbler(process.getInputStream(), processId.getKey());
-    this.stdOutGobbler.start();
-    this.stdErrGobbler = new StreamGobbler(process.getErrorStream(), processId.getKey());
-    this.stdErrGobbler.start();
-    this.stopWatcher.start();
-    this.eventWatcher.start();
-    // Could be improved by checking the status "up" in shared memory.
-    // Not a problem so far as this state is not used by listeners.
-    lifecycle.tryToMoveTo(Lifecycle.State.STARTED);
-    return true;
-  }
-
-  public ProcessId getProcessId() {
-    return processId;
-  }
-
-  Lifecycle.State getState() {
-    return lifecycle.getState();
-  }
-
-  /**
-   * Sends kill signal and awaits termination. No guarantee that process is gracefully terminated (=shutdown hooks
-   * executed). It depends on OS.
-   */
-  public void stop(long timeout, TimeUnit timeoutUnit) {
-    if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) {
-      stopGracefully(timeout, timeoutUnit);
-      if (process != null && process.isAlive()) {
-        LOG.info("{} failed to stop in a timely fashion. Killing it.", processId.getKey());
-      }
-      // enforce stop and clean-up even if process has been gracefully stopped
-      stopForcibly();
-    } else {
-      // already stopping or stopped
-      waitForDown();
-    }
-  }
-
-  private void waitForDown() {
-    while (process != null && process.isAlive()) {
-      try {
-        process.waitFor();
-      } catch (InterruptedException ignored) {
-        // ignore, waiting for process to stop
-        Thread.currentThread().interrupt();
-      }
-    }
-  }
-
-  private void stopGracefully(long timeout, TimeUnit timeoutUnit) {
-    if (process == null) {
-      return;
-    }
-    try {
-      // request graceful stop
-      process.askForStop();
-      process.waitFor(timeout, timeoutUnit);
-    } catch (InterruptedException e) {
-      // can't wait for the termination of process. Let's assume it's down.
-      LOG.warn(format("Interrupted while stopping process %s", processId), e);
-      Thread.currentThread().interrupt();
-    } catch (Throwable e) {
-      LOG.error("Can not ask for graceful stop of process " + processId, e);
-    }
-  }
-
-  public void stopForcibly() {
-    eventWatcher.interrupt();
-    stopWatcher.interrupt();
-    if (process != null) {
-      process.destroyForcibly();
-      waitForDown();
-      process.closeStreams();
-    }
-    if (stdOutGobbler != null) {
-      StreamGobbler.waitUntilFinish(stdOutGobbler);
-      stdOutGobbler.interrupt();
-    }
-    if (stdErrGobbler != null) {
-      StreamGobbler.waitUntilFinish(stdErrGobbler);
-      stdErrGobbler.interrupt();
-    }
-    lifecycle.tryToMoveTo(Lifecycle.State.STOPPED);
-  }
-
-  void refreshState() {
-    if (process.isAlive()) {
-      if (!operational.get() && process.isOperational()) {
-        operational.set(true);
-        eventListeners.forEach(l -> l.onProcessEvent(processId, ProcessEventListener.Type.OPERATIONAL));
-      }
-      if (process.askedForRestart()) {
-        process.acknowledgeAskForRestart();
-        eventListeners.forEach(l -> l.onProcessEvent(processId, ProcessEventListener.Type.ASK_FOR_RESTART));
-      }
-    } else {
-      stopForcibly();
-    }
-  }
-
-  @Override
-  public String toString() {
-    return format("Process[%s]", processId.getKey());
-  }
-
-  /**
-   * This thread blocks as long as the monitored process is physically alive.
-   * It avoids from executing {@link Process#exitValue()} at a fixed rate :
-   * <ul>
-   *   <li>no usage of exception for flow control. Indeed {@link Process#exitValue()} throws an exception
-   *   if process is alive. There's no method <code>Process#isAlive()</code></li>
-   *   <li>no delay, instantaneous notification that process is down</li>
-   * </ul>
-   */
-  private class StopWatcher extends Thread {
-    StopWatcher() {
-      // this name is different than Thread#toString(), which includes name, priority
-      // and thread group
-      // -> do not override toString()
-      super(format("StopWatcher[%s]", processId.getKey()));
-    }
-
-    @Override
-    public void run() {
-      try {
-        process.waitFor();
-      } catch (InterruptedException e) {
-        Thread.currentThread().interrupt();
-        // stop watching process
-      }
-      stopForcibly();
-    }
-  }
-
-  private class EventWatcher extends Thread {
-    EventWatcher() {
-      // this name is different than Thread#toString(), which includes name, priority
-      // and thread group
-      // -> do not override toString()
-      super(format("EventWatcher[%s]", processId.getKey()));
-    }
-
-    @Override
-    public void run() {
-      try {
-        while (process.isAlive()) {
-          refreshState();
-          Thread.sleep(watcherDelayMs);
-        }
-      } catch (InterruptedException e) {
-        // request to stop watching process. To avoid unexpected behaviors
-        // the process is stopped.
-        Thread.currentThread().interrupt();
-        stopForcibly();
-      }
-    }
-  }
-
-  public static Builder builder(ProcessId processId) {
-    return new Builder(processId);
-  }
-
-  public static class Builder {
-    private final ProcessId processId;
-    private final List<ProcessEventListener> eventListeners = new ArrayList<>();
-    private final List<ProcessLifecycleListener> lifecycleListeners = new ArrayList<>();
-    private long watcherDelayMs = DEFAULT_WATCHER_DELAY_MS;
-
-    private Builder(ProcessId processId) {
-      this.processId = processId;
-    }
-
-    public Builder addEventListener(ProcessEventListener listener) {
-      this.eventListeners.add(listener);
-      return this;
-    }
-
-    public Builder addProcessLifecycleListener(ProcessLifecycleListener listener) {
-      this.lifecycleListeners.add(listener);
-      return this;
-    }
-
-    /**
-     * Default delay is {@link #DEFAULT_WATCHER_DELAY_MS}
-     */
-    public Builder setWatcherDelayMs(long l) {
-      this.watcherDelayMs = l;
-      return this;
-    }
-
-    public SQProcess build() {
-      return new SQProcess(this);
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcher.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcher.java
deleted file mode 100644 (file)
index 2fef4ab..0000000
+++ /dev/null
@@ -1,32 +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.application.process;
-
-/**
- * Background thread that checks if a stop request
- * is sent, usually by Orchestrator
- */
-public interface StopRequestWatcher {
-
-  void startWatching();
-
-  void stopWatching();
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
deleted file mode 100644 (file)
index fc042f6..0000000
+++ /dev/null
@@ -1,91 +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.application.process;
-
-import org.sonar.application.FileSystem;
-import org.sonar.application.Scheduler;
-import org.sonar.application.config.AppSettings;
-import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
-import org.sonar.process.sharedmemoryfile.ProcessCommands;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-
-public class StopRequestWatcherImpl extends Thread implements StopRequestWatcher {
-
-  private static final long DEFAULT_WATCHER_DELAY_MS = 500L;
-
-  private final ProcessCommands commands;
-  private final Scheduler scheduler;
-  private final AppSettings settings;
-  private long delayMs = DEFAULT_WATCHER_DELAY_MS;
-
-  StopRequestWatcherImpl(AppSettings settings, Scheduler scheduler, ProcessCommands commands) {
-    super("StopRequestWatcherImpl");
-    this.settings = settings;
-    this.commands = commands;
-    this.scheduler = scheduler;
-
-    // safeguard, do not block the JVM if thread is not interrupted
-    // (method stopWatching() never called).
-    setDaemon(true);
-  }
-
-  public static StopRequestWatcherImpl create(AppSettings settings, Scheduler scheduler, FileSystem fs) {
-    DefaultProcessCommands commands = DefaultProcessCommands.secondary(fs.getTempDir(), ProcessId.APP.getIpcIndex());
-    return new StopRequestWatcherImpl(settings, scheduler, commands);
-  }
-
-  long getDelayMs() {
-    return delayMs;
-  }
-
-  void setDelayMs(long delayMs) {
-    this.delayMs = delayMs;
-  }
-
-  @Override
-  public void run() {
-    try {
-      while (true) {
-        if (commands.askedForStop()) {
-          scheduler.terminate();
-          return;
-        }
-        Thread.sleep(delayMs);
-      }
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      // stop watching the commands
-    }
-  }
-
-  @Override
-  public void startWatching() {
-    if (settings.getProps().valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND)) {
-      start();
-    }
-  }
-
-  @Override
-  public void stopWatching() {
-    // does nothing is not started
-    interrupt();
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StreamGobbler.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StreamGobbler.java
deleted file mode 100644 (file)
index b920360..0000000
+++ /dev/null
@@ -1,73 +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.application.process;
-
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import javax.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Reads process output and writes to logs
- */
-public class StreamGobbler extends Thread {
-
-  public static final String LOGGER_GOBBLER = "gobbler";
-
-  private final InputStream is;
-  private final Logger logger;
-
-  StreamGobbler(InputStream is, String processKey) {
-    this(is, processKey, LoggerFactory.getLogger(LOGGER_GOBBLER));
-  }
-
-  StreamGobbler(InputStream is, String processKey, Logger logger) {
-    super(String.format("Gobbler[%s]", processKey));
-    this.is = is;
-    this.logger = logger;
-  }
-
-  @Override
-  public void run() {
-    try (BufferedReader br = new BufferedReader(new InputStreamReader(is, UTF_8))) {
-      String line;
-      while ((line = br.readLine()) != null) {
-        logger.info(line);
-      }
-    } catch (Exception ignored) {
-      // ignored
-    }
-  }
-
-  static void waitUntilFinish(@Nullable StreamGobbler gobbler) {
-    if (gobbler != null) {
-      try {
-        gobbler.join();
-      } catch (InterruptedException ignored) {
-        // consider as finished, restore the interrupted flag
-        Thread.currentThread().interrupt();
-      }
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/package-info.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/package-info.java
deleted file mode 100644 (file)
index 26e8d8e..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.application.process;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml b/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml
deleted file mode 100644 (file)
index bca8635..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-# ======================== Elasticsearch Configuration =========================
-#
-# NOTE: Elasticsearch comes with reasonable defaults for most settings.
-#       Before you set out to tweak and tune the configuration, make sure you
-#       understand what are you trying to accomplish and the consequences.
-#
-# The primary way of configuring a node is via this file. This template lists
-# the most important settings you may want to configure for a production cluster.
-#
-# Please see the documentation for further information on configuration options:
-# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/settings.html>
-#
-# ---------------------------------- Cluster -----------------------------------
-#
-# Use a descriptive name for your cluster:
-#
-#cluster.name: my-application
-#
-# ------------------------------------ Node ------------------------------------
-#
-# Use a descriptive name for the node:
-#
-#node.name: node-1
-#
-# Add custom attributes to the node:
-#
-#node.attr.rack: r1
-#
-# ----------------------------------- Paths ------------------------------------
-#
-# Path to directory where to store the data (separate multiple locations by comma):
-#
-#path.data: /path/to/data
-#
-# Path to log files:
-#
-#path.logs: /path/to/logs
-#
-# ----------------------------------- Memory -----------------------------------
-#
-# Lock the memory on startup:
-#
-#bootstrap.memory_lock: true
-#
-# Make sure that the heap size is set to about half the memory available
-# on the system and that the owner of the process is allowed to use this
-# limit.
-#
-# Elasticsearch performs poorly when the system is swapping the memory.
-#
-# ---------------------------------- Network -----------------------------------
-#
-# Set the bind address to a specific IP (IPv4 or IPv6):
-#
-#network.host: 192.168.0.1
-#
-# Set a custom port for HTTP:
-#
-#http.port: 9200
-#
-# For more information, see the documentation at:
-# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-network.html>
-#
-# --------------------------------- Discovery ----------------------------------
-#
-# Pass an initial list of hosts to perform discovery when new node is started:
-# The default list of hosts is ["127.0.0.1", "[::1]"]
-#
-#discovery.zen.ping.unicast.hosts: ["host1", "host2"]
-#
-# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1):
-#
-#discovery.zen.minimum_master_nodes: 3
-#
-# For more information, see the documentation at:
-# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-discovery-zen.html>
-#
-# ---------------------------------- Gateway -----------------------------------
-#
-# Block initial recovery after a full cluster restart until N nodes are started:
-#
-#gateway.recover_after_nodes: 3
-#
-# For more information, see the documentation at:
-# <https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-gateway.html>
-#
-# ---------------------------------- Various -----------------------------------
-#
-# Require explicit names when deleting indices:
-#
-#action.destructive_requires_name: true
diff --git a/server/sonar-process-monitor/src/main/resources/sonarqube-version.txt b/server/sonar-process-monitor/src/main/resources/sonarqube-version.txt
deleted file mode 100644 (file)
index 6b7ce46..0000000
+++ /dev/null
@@ -1 +0,0 @@
-${buildVersion}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java
deleted file mode 100644 (file)
index 3786190..0000000
+++ /dev/null
@@ -1,197 +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.application;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
-import javax.annotation.CheckForNull;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
-import org.sonar.process.ProcessProperties;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.sharedmemoryfile.ProcessCommands.MAX_PROCESSES;
-
-public class AppFileSystemTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private File homeDir;
-  private File dataDir;
-  private File tempDir;
-  private File logsDir;
-  private File webDir;
-  private TestAppSettings settings = new TestAppSettings();
-  private AppFileSystem underTest = new AppFileSystem(settings);
-
-  @Before
-  public void before() throws IOException {
-    homeDir = temp.newFolder();
-    dataDir = new File(homeDir, "data");
-    tempDir = new File(homeDir, "temp");
-    logsDir = new File(homeDir, "logs");
-    webDir = new File(homeDir, "web");
-
-    settings.getProps().set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
-    settings.getProps().set(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath());
-    settings.getProps().set(ProcessProperties.PATH_TEMP, tempDir.getAbsolutePath());
-    settings.getProps().set(ProcessProperties.PATH_LOGS, logsDir.getAbsolutePath());
-    settings.getProps().set(ProcessProperties.PATH_WEB, webDir.getAbsolutePath());
-  }
-
-  @Test
-  public void reset_creates_dirs_if_they_don_t_exist() throws Exception {
-    assertThat(dataDir).doesNotExist();
-
-    underTest.reset();
-
-    assertThat(dataDir).exists().isDirectory();
-    assertThat(logsDir).exists().isDirectory();
-    assertThat(tempDir).exists().isDirectory();
-    assertThat(webDir).exists().isDirectory();
-
-    underTest.reset();
-
-    assertThat(dataDir).exists().isDirectory();
-    assertThat(logsDir).exists().isDirectory();
-    assertThat(tempDir).exists().isDirectory();
-    assertThat(webDir).exists().isDirectory();
-  }
-
-  @Test
-  public void reset_deletes_content_of_temp_dir_but_not_temp_dir_itself_if_it_already_exists() throws Exception {
-    assertThat(tempDir.mkdir()).isTrue();
-    Object tempDirKey = getFileKey(tempDir);
-    File fileInTempDir = new File(tempDir, "someFile.txt");
-    assertThat(fileInTempDir.createNewFile()).isTrue();
-    File subDirInTempDir = new File(tempDir, "subDir");
-    assertThat(subDirInTempDir.mkdir()).isTrue();
-
-    underTest.reset();
-
-    assertThat(tempDir).exists();
-    assertThat(fileInTempDir).doesNotExist();
-    assertThat(subDirInTempDir).doesNotExist();
-    assertThat(getFileKey(tempDir)).isEqualTo(tempDirKey);
-  }
-
-  @Test
-  public void reset_deletes_content_of_temp_dir_but_not_sharedmemory_file() throws Exception {
-    assertThat(tempDir.mkdir()).isTrue();
-    File sharedmemory = new File(tempDir, "sharedmemory");
-    assertThat(sharedmemory.createNewFile()).isTrue();
-    FileUtils.write(sharedmemory, "toto");
-    Object fileKey = getFileKey(sharedmemory);
-
-    Object tempDirKey = getFileKey(tempDir);
-    File fileInTempDir = new File(tempDir, "someFile.txt");
-    assertThat(fileInTempDir.createNewFile()).isTrue();
-
-    underTest.reset();
-
-    assertThat(tempDir).exists();
-    assertThat(fileInTempDir).doesNotExist();
-    assertThat(getFileKey(tempDir)).isEqualTo(tempDirKey);
-    assertThat(getFileKey(sharedmemory)).isEqualTo(fileKey);
-    // content of sharedMemory file is reset
-    assertThat(FileUtils.readFileToString(sharedmemory)).isNotEqualTo("toto");
-  }
-
-  @Test
-  public void reset_cleans_the_sharedmemory_file() throws IOException {
-    assertThat(tempDir.mkdir()).isTrue();
-    try (AllProcessesCommands commands = new AllProcessesCommands(tempDir)) {
-      for (int i = 0; i < MAX_PROCESSES; i++) {
-        commands.create(i).setUp();
-      }
-
-      underTest.reset();
-
-      for (int i = 0; i < MAX_PROCESSES; i++) {
-        assertThat(commands.create(i).isUp()).isFalse();
-      }
-    }
-  }
-
-  @CheckForNull
-  private static Object getFileKey(File fileInTempDir) throws IOException {
-    Path path = Paths.get(fileInTempDir.toURI());
-    BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
-    return attrs.fileKey();
-  }
-
-  @Test
-  public void reset_throws_ISE_if_data_dir_is_a_file() throws Exception {
-    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_DATA);
-  }
-
-  @Test
-  public void reset_throws_ISE_if_web_dir_is_a_file() throws Exception {
-    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_WEB);
-  }
-
-  @Test
-  public void reset_throws_ISE_if_logs_dir_is_a_file() throws Exception {
-    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_LOGS);
-  }
-
-  @Test
-  public void reset_throws_ISE_if_temp_dir_is_a_file() throws Exception {
-    resetThrowsISEIfDirIsAFile(ProcessProperties.PATH_TEMP);
-  }
-
-  private void resetThrowsISEIfDirIsAFile(String property) throws IOException {
-    File file = new File(homeDir, "zoom.store");
-    assertThat(file.createNewFile()).isTrue();
-    settings.getProps().set(property, file.getAbsolutePath());
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Property '" + property + "' is not valid, not a directory: " + file.getAbsolutePath());
-
-    underTest.reset();
-  }
-
-  @Test
-  public void fail_if_required_directory_is_a_file() throws Exception {
-    // <home>/data is missing
-    FileUtils.forceMkdir(webDir);
-    FileUtils.forceMkdir(logsDir);
-    FileUtils.touch(dataDir);
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Property 'sonar.path.data' is not valid, not a directory: " + dataDir.getAbsolutePath());
-
-    underTest.reset();
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppLoggingTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppLoggingTest.java
deleted file mode 100644 (file)
index 61b5feb..0000000
+++ /dev/null
@@ -1,323 +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.application;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.FileAppender;
-import ch.qos.logback.core.encoder.Encoder;
-import ch.qos.logback.core.joran.spi.ConsoleTarget;
-import ch.qos.logback.core.rolling.RollingFileAppender;
-import java.io.File;
-import java.io.IOException;
-import java.util.Iterator;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.logging.LogbackHelper;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.slf4j.Logger.ROOT_LOGGER_NAME;
-import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
-
-public class AppLoggingTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private File logDir;
-
-  private AppSettings settings = new TestAppSettings();
-  private AppLogging underTest = new AppLogging(settings);
-
-  @Before
-  public void setUp() throws Exception {
-    logDir = temp.newFolder();
-    settings.getProps().set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
-  }
-
-  @AfterClass
-  public static void resetLogback() throws Exception {
-    new LogbackHelper().resetFromXml("/logback-test.xml");
-  }
-
-  @Test
-  public void no_writing_to_sonar_log_file_when_running_from_sonar_script() {
-    emulateRunFromSonarScript();
-
-    LoggerContext ctx = underTest.configure();
-
-    ctx.getLoggerList().forEach(AppLoggingTest::verifyNoFileAppender);
-  }
-
-  @Test
-  public void root_logger_only_writes_to_console_with_formatting_when_running_from_sonar_script() {
-    emulateRunFromSonarScript();
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) rootLogger.getAppender("APP_CONSOLE");
-    verifyAppFormattedLogEncoder(consoleAppender.getEncoder());
-    assertThat(rootLogger.iteratorForAppenders()).hasSize(1);
-  }
-
-  @Test
-  public void gobbler_logger_writes_to_console_without_formatting_when_running_from_sonar_script() {
-    emulateRunFromSonarScript();
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
-    verifyGobblerConsoleAppender(gobblerLogger);
-    assertThat(gobblerLogger.iteratorForAppenders()).hasSize(1);
-  }
-
-  @Test
-  public void root_logger_writes_to_console_with_formatting_and_to_sonar_log_file_when_running_from_command_line() {
-    emulateRunFromCommandLine(false);
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    verifyAppConsoleAppender(rootLogger.getAppender("APP_CONSOLE"));
-    verifySonarLogFileAppender(rootLogger.getAppender("file_sonar"));
-    assertThat(rootLogger.iteratorForAppenders()).hasSize(2);
-
-    // verify no other logger writes to sonar.log
-    ctx.getLoggerList()
-      .stream()
-      .filter(logger -> !ROOT_LOGGER_NAME.equals(logger.getName()))
-      .forEach(AppLoggingTest::verifyNoFileAppender);
-  }
-
-  @Test
-  public void gobbler_logger_writes_to_console_without_formatting_when_running_from_command_line() {
-    emulateRunFromCommandLine(false);
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
-    verifyGobblerConsoleAppender(gobblerLogger);
-    assertThat(gobblerLogger.iteratorForAppenders()).hasSize(1);
-  }
-
-  @Test
-  public void root_logger_writes_to_console_with_formatting_and_to_sonar_log_file_when_running_from_ITs() {
-    emulateRunFromCommandLine(true);
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    verifyAppConsoleAppender(rootLogger.getAppender("APP_CONSOLE"));
-    verifySonarLogFileAppender(rootLogger.getAppender("file_sonar"));
-    assertThat(rootLogger.iteratorForAppenders()).hasSize(2);
-
-    ctx.getLoggerList()
-      .stream()
-      .filter(logger -> !ROOT_LOGGER_NAME.equals(logger.getName()))
-      .forEach(AppLoggingTest::verifyNoFileAppender);
-  }
-
-  @Test
-  public void gobbler_logger_writes_to_console_without_formatting_when_running_from_ITs() {
-    emulateRunFromCommandLine(true);
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
-    verifyGobblerConsoleAppender(gobblerLogger);
-    assertThat(gobblerLogger.iteratorForAppenders()).hasSize(1);
-  }
-
-  @Test
-  public void configure_no_rotation_on_sonar_file() {
-    settings.getProps().set("sonar.log.rollingPolicy", "none");
-
-    LoggerContext ctx = underTest.configure();
-
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    Appender<ILoggingEvent> appender = rootLogger.getAppender("file_sonar");
-    assertThat(appender)
-      .isNotInstanceOf(RollingFileAppender.class)
-      .isInstanceOf(FileAppender.class);
-  }
-
-  @Test
-  public void default_level_for_root_logger_is_INFO() {
-    LoggerContext ctx = underTest.configure();
-
-    verifyRootLogLevel(ctx, Level.INFO);
-  }
-
-  @Test
-  public void root_logger_level_changes_with_global_property() {
-    settings.getProps().set("sonar.log.level", "TRACE");
-
-    LoggerContext ctx = underTest.configure();
-
-    verifyRootLogLevel(ctx, Level.TRACE);
-  }
-
-  @Test
-  public void root_logger_level_changes_with_app_property() {
-    settings.getProps().set("sonar.log.level.app", "TRACE");
-
-    LoggerContext ctx = underTest.configure();
-
-    verifyRootLogLevel(ctx, Level.TRACE);
-  }
-
-  @Test
-  public void root_logger_level_is_configured_from_app_property_over_global_property() {
-    settings.getProps().set("sonar.log.level", "TRACE");
-    settings.getProps().set("sonar.log.level.app", "DEBUG");
-
-    LoggerContext ctx = underTest.configure();
-
-    verifyRootLogLevel(ctx, Level.DEBUG);
-  }
-
-  @Test
-  public void root_logger_level_changes_with_app_property_and_is_case_insensitive() {
-    settings.getProps().set("sonar.log.level.app", "debug");
-
-    LoggerContext ctx = underTest.configure();
-
-    verifyRootLogLevel(ctx, Level.DEBUG);
-  }
-
-  @Test
-  public void default_to_INFO_if_app_property_has_invalid_value() {
-    settings.getProps().set("sonar.log.level.app", "DodoDouh!");
-
-    LoggerContext ctx = underTest.configure();
-    verifyRootLogLevel(ctx, Level.INFO);
-  }
-
-  @Test
-  public void fail_with_IAE_if_global_property_unsupported_level() {
-    settings.getProps().set("sonar.log.level", "ERROR");
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
-
-    underTest.configure();
-  }
-
-  @Test
-  public void fail_with_IAE_if_app_property_unsupported_level() {
-    settings.getProps().set("sonar.log.level.app", "ERROR");
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("log level ERROR in property sonar.log.level.app is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
-
-    underTest.configure();
-  }
-
-  @Test
-  public void no_info_log_from_hazelcast() throws IOException {
-    settings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
-    underTest.configure();
-
-    assertThat(
-      LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(false);
-  }
-
-  @Test
-  public void configure_logging_for_hazelcast() throws IOException {
-    settings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
-    settings.getProps().set(ProcessProperties.HAZELCAST_LOG_LEVEL, "INFO");
-    underTest.configure();
-
-    assertThat(
-      LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(true);
-    assertThat(
-      LoggerFactory.getLogger("com.hazelcast").isDebugEnabled()).isEqualTo(false);
-  }
-
-  private void emulateRunFromSonarScript() {
-    settings.getProps().set("sonar.wrapped", "true");
-  }
-
-  private void emulateRunFromCommandLine(boolean withAllLogsPrintedToConsole) {
-    if (withAllLogsPrintedToConsole) {
-      settings.getProps().set("sonar.log.console", "true");
-    }
-  }
-
-  private static void verifyNoFileAppender(Logger logger) {
-    Iterator<Appender<ILoggingEvent>> iterator = logger.iteratorForAppenders();
-    while (iterator.hasNext()) {
-      assertThat(iterator.next()).isNotInstanceOf(FileAppender.class);
-    }
-  }
-
-  private void verifySonarLogFileAppender(Appender<ILoggingEvent> appender) {
-    assertThat(appender).isInstanceOf(FileAppender.class);
-    FileAppender fileAppender = (FileAppender) appender;
-    assertThat(fileAppender.getFile()).isEqualTo(new File(logDir, "sonar.log").getAbsolutePath());
-    verifyAppFormattedLogEncoder(fileAppender.getEncoder());
-  }
-
-  private void verifyAppConsoleAppender(Appender<ILoggingEvent> appender) {
-    assertThat(appender).isInstanceOf(ConsoleAppender.class);
-    ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) appender;
-    assertThat(consoleAppender.getTarget()).isEqualTo(ConsoleTarget.SystemOut.getName());
-    verifyAppFormattedLogEncoder(consoleAppender.getEncoder());
-  }
-
-  private void verifyAppFormattedLogEncoder(Encoder<ILoggingEvent> encoder) {
-    verifyFormattedLogEncoder(encoder, "%d{yyyy.MM.dd HH:mm:ss} %-5level app[][%logger{20}] %msg%n");
-  }
-
-  private void verifyGobblerConsoleAppender(Logger logger) {
-    Appender<ILoggingEvent> appender = logger.getAppender("GOBBLER_CONSOLE");
-    assertThat(appender).isInstanceOf(ConsoleAppender.class);
-    ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) appender;
-    assertThat(consoleAppender.getTarget()).isEqualTo(ConsoleTarget.SystemOut.getName());
-    verifyFormattedLogEncoder(consoleAppender.getEncoder(), "%msg%n");
-  }
-
-  private void verifyFormattedLogEncoder(Encoder<ILoggingEvent> encoder, String logPattern) {
-    assertThat(encoder).isInstanceOf(PatternLayoutEncoder.class);
-    PatternLayoutEncoder patternEncoder = (PatternLayoutEncoder) encoder;
-    assertThat(patternEncoder.getPattern()).isEqualTo(logPattern);
-  }
-
-  private void verifyRootLogLevel(LoggerContext ctx, Level expected) {
-    Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
-    assertThat(rootLogger.getLevel()).isEqualTo(expected);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppReloaderImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppReloaderImplTest.java
deleted file mode 100644 (file)
index 972720a..0000000
+++ /dev/null
@@ -1,110 +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.application;
-
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.AppSettingsLoader;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.MessageException;
-import org.sonar.process.ProcessProperties;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-public class AppReloaderImplTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private AppSettingsLoader settingsLoader = mock(AppSettingsLoader.class);
-  private FileSystem fs = mock(FileSystem.class);
-  private AppState state = mock(AppState.class);
-  private AppLogging logging = mock(AppLogging.class);
-  private AppReloaderImpl underTest = new AppReloaderImpl(settingsLoader, fs, state, logging);
-
-  @Test
-  public void reload_configuration_then_reset_all() throws IOException {
-    AppSettings settings = new TestAppSettings().set("foo", "bar");
-    AppSettings newSettings = new TestAppSettings()
-      .set("foo", "newBar")
-      .set("newProp", "newVal");
-    when(settingsLoader.load()).thenReturn(newSettings);
-
-    underTest.reload(settings);
-
-    assertThat(settings.getProps().rawProperties())
-      .contains(entry("foo", "newBar"))
-      .contains(entry("newProp", "newVal"));
-    verify(logging).configure();
-    verify(state).reset();
-    verify(fs).reset();
-  }
-
-  @Test
-  public void throw_ISE_if_cluster_is_enabled() throws IOException {
-    AppSettings settings = new TestAppSettings().set(ProcessProperties.CLUSTER_ENABLED, "true");
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Restart is not possible with cluster mode");
-
-    underTest.reload(settings);
-
-    verifyZeroInteractions(logging);
-    verifyZeroInteractions(state);
-    verifyZeroInteractions(fs);
-  }
-
-  @Test
-  public void throw_MessageException_if_path_properties_are_changed() throws IOException {
-    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_DATA);
-    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_LOGS);
-    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_TEMP);
-    verifyFailureIfPropertyValueChanged(ProcessProperties.PATH_WEB);
-  }
-
-  @Test
-  public void throw_MessageException_if_cluster_mode_changed() throws IOException {
-    verifyFailureIfPropertyValueChanged(ProcessProperties.CLUSTER_ENABLED);
-  }
-
-  private void verifyFailureIfPropertyValueChanged(String propertyKey) throws IOException {
-    AppSettings settings = new TestAppSettings().set(propertyKey, "val1");
-    AppSettings newSettings = new TestAppSettings()
-      .set(propertyKey, "val2");
-    when(settingsLoader.load()).thenReturn(newSettings);
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Property [" + propertyKey + "] cannot be changed on restart: [val1] => [val2]");
-
-    underTest.reload(settings);
-
-    verifyZeroInteractions(logging);
-    verifyZeroInteractions(state);
-    verifyZeroInteractions(fs);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java
deleted file mode 100644 (file)
index ffffcc0..0000000
+++ /dev/null
@@ -1,48 +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.application;
-
-import org.junit.Test;
-import org.sonar.application.cluster.AppStateClusterImpl;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.ProcessProperties;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class AppStateFactoryTest {
-
-  private TestAppSettings settings = new TestAppSettings();
-  private AppStateFactory underTest = new AppStateFactory(settings);
-
-  @Test
-  public void create_cluster_implementation_if_cluster_is_enabled() {
-    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_NAME, "foo");
-
-    AppState appState = underTest.create();
-    assertThat(appState).isInstanceOf(AppStateClusterImpl.class);
-    ((AppStateClusterImpl) appState).close();
-  }
-
-  @Test
-  public void cluster_implementation_is_disabled_by_default() {
-    assertThat(underTest.create()).isInstanceOf(AppStateImpl.class);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java
deleted file mode 100644 (file)
index e49c5f5..0000000
+++ /dev/null
@@ -1,84 +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.application;
-
-import org.junit.Test;
-import org.sonar.process.ProcessId;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-public class AppStateImplTest {
-
-  private AppStateListener listener = mock(AppStateListener.class);
-  private AppStateImpl underTest = new AppStateImpl();
-
-  @Test
-  public void get_and_set_operational_flag() {
-    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isFalse();
-    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isFalse();
-    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isFalse();
-
-    underTest.setOperational(ProcessId.ELASTICSEARCH);
-
-    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isFalse();
-    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isTrue();
-    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isFalse();
-
-    // only local mode is supported. App state = local state
-    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, false)).isFalse();
-    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, false)).isTrue();
-    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, false)).isFalse();
-  }
-
-  @Test
-  public void notify_listeners_when_a_process_becomes_operational() {
-    underTest.addListener(listener);
-
-    underTest.setOperational(ProcessId.ELASTICSEARCH);
-
-    verify(listener).onAppStateOperational(ProcessId.ELASTICSEARCH);
-    verifyNoMoreInteractions(listener);
-  }
-
-  @Test
-  public void tryToLockWebLeader_returns_true_if_first_call() {
-    assertThat(underTest.tryToLockWebLeader()).isTrue();
-
-    // next calls return false
-    assertThat(underTest.tryToLockWebLeader()).isFalse();
-    assertThat(underTest.tryToLockWebLeader()).isFalse();
-  }
-
-  @Test
-  public void reset_initializes_all_flags() {
-    underTest.setOperational(ProcessId.ELASTICSEARCH);
-    assertThat(underTest.tryToLockWebLeader()).isTrue();
-
-    underTest.reset();
-
-    assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isFalse();
-    assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isFalse();
-    assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isFalse();
-    assertThat(underTest.tryToLockWebLeader()).isTrue();
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java
deleted file mode 100644 (file)
index f733682..0000000
+++ /dev/null
@@ -1,476 +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.application;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.EnumMap;
-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;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.application.process.ProcessLauncher;
-import org.sonar.application.process.ProcessMonitor;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-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 static java.util.Collections.synchronizedList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
-import static org.sonar.process.ProcessId.ELASTICSEARCH;
-import static org.sonar.process.ProcessId.WEB_SERVER;
-
-public class SchedulerImplTest {
-
-  @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();
-  private TestCommandFactory javaCommandFactory = new TestCommandFactory();
-  private TestProcessLauncher processLauncher = new TestProcessLauncher();
-  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();
-  }
-
-  @Test
-  public void start_and_stop_sequence_of_ES_WEB_CE_in_order() throws Exception {
-    enableAllProcesses();
-    SchedulerImpl underTest = newScheduler();
-    underTest.schedule();
-
-    // elasticsearch does not have preconditions to start
-    TestProcess es = processLauncher.waitForProcess(ELASTICSEARCH);
-    assertThat(es.isAlive()).isTrue();
-    assertThat(processLauncher.processes).hasSize(1);
-
-    // elasticsearch becomes operational -> web leader is starting
-    es.operational = true;
-    waitForAppStateOperational(ELASTICSEARCH);
-    TestProcess web = processLauncher.waitForProcess(WEB_SERVER);
-    assertThat(web.isAlive()).isTrue();
-    assertThat(processLauncher.processes).hasSize(2);
-    assertThat(processLauncher.commands).containsExactly(esCommand, webLeaderCommand);
-
-    // web becomes operational -> CE is starting
-    web.operational = true;
-    waitForAppStateOperational(WEB_SERVER);
-    TestProcess ce = processLauncher.waitForProcess(COMPUTE_ENGINE);
-    assertThat(ce.isAlive()).isTrue();
-    assertThat(processLauncher.processes).hasSize(3);
-    assertThat(processLauncher.commands).containsExactly(esCommand, webLeaderCommand, ceCommand);
-
-    // all processes are up
-    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isTrue());
-
-    // processes are stopped in reverse order of startup
-    underTest.terminate();
-    assertThat(orderedStops).containsExactly(COMPUTE_ENGINE, WEB_SERVER, ELASTICSEARCH);
-    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
-
-    // does nothing because scheduler is already terminated
-    underTest.awaitTermination();
-  }
-
-  private void enableAllProcesses() {
-    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
-  }
-
-  @Test
-  public void all_processes_are_stopped_if_one_process_goes_down() throws Exception {
-    Scheduler underTest = startAll();
-
-    processLauncher.waitForProcess(WEB_SERVER).destroyForcibly();
-
-    underTest.awaitTermination();
-    assertThat(orderedStops).containsExactly(WEB_SERVER, COMPUTE_ENGINE, ELASTICSEARCH);
-    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
-
-    // following does nothing
-    underTest.terminate();
-    underTest.awaitTermination();
-  }
-
-  @Test
-  public void all_processes_are_stopped_if_one_process_fails_to_start() throws Exception {
-    enableAllProcesses();
-    SchedulerImpl underTest = newScheduler();
-    processLauncher.makeStartupFail = COMPUTE_ENGINE;
-
-    underTest.schedule();
-
-    processLauncher.waitForProcess(ELASTICSEARCH).operational = true;
-    processLauncher.waitForProcess(WEB_SERVER).operational = true;
-
-    underTest.awaitTermination();
-    assertThat(orderedStops).containsExactly(WEB_SERVER, ELASTICSEARCH);
-    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
-  }
-
-  @Test
-  public void terminate_can_be_called_multiple_times() throws Exception {
-    Scheduler underTest = startAll();
-
-    underTest.terminate();
-    processLauncher.processes.values().forEach(p -> assertThat(p.isAlive()).isFalse());
-
-    // does nothing
-    underTest.terminate();
-  }
-
-  @Test
-  public void awaitTermination_blocks_until_all_processes_are_stopped() throws Exception {
-    Scheduler underTest = startAll();
-
-    Thread awaitingTermination = new Thread(() -> underTest.awaitTermination());
-    awaitingTermination.start();
-    assertThat(awaitingTermination.isAlive()).isTrue();
-
-    underTest.terminate();
-    // the thread is being stopped
-    awaitingTermination.join();
-    assertThat(awaitingTermination.isAlive()).isFalse();
-  }
-
-  @Test
-  @Ignore("false-positives on Travis CI")
-  public void restart_reloads_java_commands_and_restarts_all_processes() throws Exception {
-    Scheduler underTest = startAll();
-
-    processLauncher.waitForProcess(WEB_SERVER).askedForRestart = true;
-
-    // waiting for all processes to be stopped
-    boolean stopped = false;
-    while (!stopped) {
-      stopped = orderedStops.size() == 3;
-      Thread.sleep(1L);
-    }
-
-    // restarting
-    verify(appReloader, timeout(60_000)).reload(settings);
-    processLauncher.waitForProcessAlive(ELASTICSEARCH);
-    processLauncher.waitForProcessAlive(COMPUTE_ENGINE);
-    processLauncher.waitForProcessAlive(WEB_SERVER);
-
-    underTest.terminate();
-    // 3+3 processes have been stopped
-    assertThat(orderedStops).hasSize(6);
-    assertThat(processLauncher.waitForProcess(ELASTICSEARCH).isAlive()).isFalse();
-    assertThat(processLauncher.waitForProcess(COMPUTE_ENGINE).isAlive()).isFalse();
-    assertThat(processLauncher.waitForProcess(WEB_SERVER).isAlive()).isFalse();
-
-    // verify that awaitTermination() does not block
-    underTest.awaitTermination();
-  }
-
-  @Test
-  public void restart_stops_all_if_new_settings_are_not_allowed() throws Exception {
-    Scheduler underTest = startAll();
-    doThrow(new IllegalStateException("reload error")).when(appReloader).reload(settings);
-
-    processLauncher.waitForProcess(WEB_SERVER).askedForRestart = true;
-
-    // waiting for all processes to be stopped
-    processLauncher.waitForProcessDown(ELASTICSEARCH);
-    processLauncher.waitForProcessDown(COMPUTE_ENGINE);
-    processLauncher.waitForProcessDown(WEB_SERVER);
-
-    // verify that awaitTermination() does not block
-    underTest.awaitTermination();
-  }
-
-  @Test
-  public void web_follower_starts_only_when_web_leader_is_operational() throws Exception {
-    // leader takes the lock, so underTest won't get it
-    assertThat(appState.tryToLockWebLeader()).isTrue();
-
-    appState.setOperational(ProcessId.ELASTICSEARCH);
-    enableAllProcesses();
-    SchedulerImpl underTest = newScheduler();
-    underTest.schedule();
-
-    processLauncher.waitForProcessAlive(ProcessId.ELASTICSEARCH);
-    assertThat(processLauncher.processes).hasSize(1);
-
-    // leader becomes operational -> follower can start
-    appState.setOperational(ProcessId.WEB_SERVER);
-    processLauncher.waitForProcessAlive(WEB_SERVER);
-
-    underTest.terminate();
-  }
-
-  @Test
-  public void web_server_waits_for_remote_elasticsearch_to_be_started_if_local_es_is_disabled() throws Exception {
-    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
-    SchedulerImpl underTest = newScheduler();
-    underTest.schedule();
-
-    // WEB and CE wait for ES to be up
-    assertThat(processLauncher.processes).isEmpty();
-
-    // ES becomes operational on another node -> web leader can start
-    appState.setRemoteOperational(ProcessId.ELASTICSEARCH);
-    processLauncher.waitForProcessAlive(WEB_SERVER);
-    assertThat(processLauncher.processes).hasSize(1);
-
-    underTest.terminate();
-  }
-
-  @Test
-  public void compute_engine_waits_for_remote_elasticsearch_and_web_leader_to_be_started_if_local_es_is_disabled() throws Exception {
-    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
-    SchedulerImpl underTest = newScheduler();
-    underTest.schedule();
-
-    // CE waits for ES and WEB leader to be up
-    assertThat(processLauncher.processes).isEmpty();
-
-    // ES and WEB leader become operational on another nodes -> CE can start
-    appState.setRemoteOperational(ProcessId.ELASTICSEARCH);
-    appState.setRemoteOperational(ProcessId.WEB_SERVER);
-
-    processLauncher.waitForProcessAlive(COMPUTE_ENGINE);
-    assertThat(processLauncher.processes).hasSize(1);
-
-    underTest.terminate();
-  }
-
-  private SchedulerImpl newScheduler() {
-    return new SchedulerImpl(settings, appReloader, javaCommandFactory, processLauncher, appState)
-      .setProcessWatcherDelayMs(1L);
-  }
-
-  private Scheduler startAll() throws InterruptedException {
-    enableAllProcesses();
-    SchedulerImpl scheduler = newScheduler();
-    scheduler.schedule();
-    processLauncher.waitForProcess(ELASTICSEARCH).operational = true;
-    processLauncher.waitForProcess(WEB_SERVER).operational = true;
-    processLauncher.waitForProcess(COMPUTE_ENGINE).operational = true;
-    return scheduler;
-  }
-
-  private void waitForAppStateOperational(ProcessId id) throws InterruptedException {
-    while (true) {
-      if (appState.isOperational(id, true)) {
-        return;
-      }
-      Thread.sleep(1L);
-    }
-  }
-
-  private class TestCommandFactory implements CommandFactory {
-    @Override
-    public EsCommand createEsCommand() {
-      return esCommand;
-    }
-
-    @Override
-    public JavaCommand createWebCommand(boolean leader) {
-      return leader ? webLeaderCommand : webFollowerCommand;
-    }
-
-    @Override
-    public JavaCommand createCeCommand() {
-      return ceCommand;
-    }
-  }
-
-  private class TestProcessLauncher implements ProcessLauncher {
-    private final EnumMap<ProcessId, TestProcess> processes = new EnumMap<>(ProcessId.class);
-    private final List<AbstractCommand<?>> commands = synchronizedList(new ArrayList<>());
-    private ProcessId makeStartupFail = null;
-
-    @Override
-    public ProcessMonitor launch(EsCommand esCommand) {
-      return launchImpl(esCommand);
-    }
-
-    @Override
-    public ProcessMonitor launch(JavaCommand javaCommand) {
-      return launchImpl(javaCommand);
-    }
-
-    private ProcessMonitor launchImpl(AbstractCommand<?> javaCommand) {
-      commands.add(javaCommand);
-      if (makeStartupFail == javaCommand.getProcessId()) {
-        throw new IllegalStateException("cannot start " + javaCommand.getProcessId());
-      }
-      TestProcess process = new TestProcess(javaCommand.getProcessId());
-      processes.put(javaCommand.getProcessId(), process);
-      return process;
-    }
-
-    private TestProcess waitForProcess(ProcessId id) throws InterruptedException {
-      while (true) {
-        TestProcess p = processes.get(id);
-        if (p != null) {
-          return p;
-        }
-        Thread.sleep(1L);
-      }
-    }
-
-    private TestProcess waitForProcessAlive(ProcessId id) throws InterruptedException {
-      while (true) {
-        TestProcess p = processes.get(id);
-        if (p != null && p.isAlive()) {
-          return p;
-        }
-        Thread.sleep(1L);
-      }
-    }
-
-    private TestProcess waitForProcessDown(ProcessId id) throws InterruptedException {
-      while (true) {
-        TestProcess p = processes.get(id);
-        if (p != null && !p.isAlive()) {
-          return p;
-        }
-        Thread.sleep(1L);
-      }
-    }
-
-    @Override
-    public void close() {
-      for (TestProcess process : processes.values()) {
-        process.destroyForcibly();
-      }
-    }
-  }
-
-  private class TestProcess implements ProcessMonitor, AutoCloseable {
-    private final ProcessId processId;
-    private final CountDownLatch alive = new CountDownLatch(1);
-    private boolean operational = false;
-    private boolean askedForRestart = false;
-
-    private TestProcess(ProcessId processId) {
-      this.processId = processId;
-    }
-
-    @Override
-    public InputStream getInputStream() {
-      return mock(InputStream.class, Mockito.RETURNS_MOCKS);
-    }
-
-    @Override
-    public InputStream getErrorStream() {
-      return mock(InputStream.class, Mockito.RETURNS_MOCKS);
-    }
-
-    @Override
-    public void closeStreams() {
-    }
-
-    @Override
-    public boolean isAlive() {
-      return alive.getCount() == 1;
-    }
-
-    @Override
-    public void askForStop() {
-      destroyForcibly();
-    }
-
-    @Override
-    public void destroyForcibly() {
-      if (isAlive()) {
-        orderedStops.add(processId);
-      }
-      alive.countDown();
-    }
-
-    @Override
-    public void waitFor() throws InterruptedException {
-      alive.await();
-    }
-
-    @Override
-    public void waitFor(long timeout, TimeUnit timeoutUnit) throws InterruptedException {
-      alive.await(timeout, timeoutUnit);
-    }
-
-    @Override
-    public boolean isOperational() {
-      return operational;
-    }
-
-    @Override
-    public boolean askedForRestart() {
-      return askedForRestart;
-    }
-
-    @Override
-    public void acknowledgeAskForRestart() {
-      this.askedForRestart = false;
-    }
-
-    @Override
-    public void close() {
-      alive.countDown();
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java
deleted file mode 100644 (file)
index d01b129..0000000
+++ /dev/null
@@ -1,89 +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.application;
-
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import javax.annotation.Nonnull;
-import org.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-
-public class TestAppState implements AppState {
-
-  private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class);
-  private final Map<ProcessId, Boolean> remoteProcesses = new EnumMap<>(ProcessId.class);
-  private final List<AppStateListener> listeners = new ArrayList<>();
-  private final AtomicBoolean webLeaderLocked = new AtomicBoolean(false);
-
-  @Override
-  public void addListener(@Nonnull AppStateListener listener) {
-    this.listeners.add(listener);
-  }
-
-  @Override
-  public boolean isOperational(ProcessId processId, boolean local) {
-    if (local) {
-      return localProcesses.computeIfAbsent(processId, p -> false);
-    }
-    return remoteProcesses.computeIfAbsent(processId, p -> false);
-  }
-
-  @Override
-  public void setOperational(ProcessId processId) {
-    localProcesses.put(processId, true);
-    remoteProcesses.put(processId, true);
-    listeners.forEach(l -> l.onAppStateOperational(processId));
-  }
-
-  public void setRemoteOperational(ProcessId processId) {
-    remoteProcesses.put(processId, true);
-    listeners.forEach(l -> l.onAppStateOperational(processId));
-  }
-
-  @Override
-  public boolean tryToLockWebLeader() {
-    return webLeaderLocked.compareAndSet(false, true);
-  }
-
-  @Override
-  public void reset() {
-    webLeaderLocked.set(false);
-    localProcesses.clear();
-  }
-
-  @Override
-  public void registerSonarQubeVersion(String sonarqubeVersion) {
-    // nothing to do
-  }
-
-  @Override
-  public Optional<String> getLeaderHostName() {
-    return Optional.of(NetworkUtils.getHostName());
-  }
-
-  @Override
-  public void close() {
-    // nothing to do
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java
deleted file mode 100644 (file)
index fd0497c..0000000
+++ /dev/null
@@ -1,150 +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.application.cluster;
-
-import com.hazelcast.core.HazelcastInstance;
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.slf4j.Logger;
-import org.sonar.application.AppStateListener;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.sonar.application.cluster.HazelcastTestHelper.createHazelcastClient;
-import static org.sonar.application.cluster.HazelcastTestHelper.newClusterSettings;
-import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
-
-public class AppStateClusterImplTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
-  @Test
-  public void instantiation_throws_ISE_if_cluster_mode_is_disabled() throws Exception {
-    TestAppSettings settings = new TestAppSettings();
-    settings.set(ProcessProperties.CLUSTER_ENABLED, "false");
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Cluster is not enabled on this instance");
-
-    new AppStateClusterImpl(settings);
-  }
-
-  @Test
-  public void tryToLockWebLeader_returns_true_only_for_the_first_call() throws Exception {
-    TestAppSettings settings = newClusterSettings();
-
-    try (AppStateClusterImpl underTest = new AppStateClusterImpl(settings)) {
-      assertThat(underTest.tryToLockWebLeader()).isEqualTo(true);
-      assertThat(underTest.tryToLockWebLeader()).isEqualTo(false);
-    }
-  }
-
-  @Test
-  public void log_when_sonarqube_is_joining_a_cluster () throws IOException, InterruptedException, IllegalAccessException, NoSuchFieldException {
-    // Now launch an instance that try to be part of the hzInstance cluster
-    TestAppSettings settings = newClusterSettings();
-
-    Logger logger = mock(Logger.class);
-    AppStateClusterImpl.setLogger(logger);
-
-    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
-      verify(logger).info(
-        eq("Joined the cluster [{}] that contains the following hosts : [{}]"),
-        eq("sonarqube"),
-        anyString()
-      );
-    }
-  }
-
-  @Test
-  public void test_listeners() throws InterruptedException {
-    AppStateListener listener = mock(AppStateListener.class);
-    try (AppStateClusterImpl underTest = new AppStateClusterImpl(newClusterSettings())) {
-      underTest.addListener(listener);
-
-      underTest.setOperational(ProcessId.ELASTICSEARCH);
-      verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
-
-      assertThat(underTest.isOperational(ProcessId.ELASTICSEARCH, true)).isEqualTo(true);
-      assertThat(underTest.isOperational(ProcessId.APP, true)).isEqualTo(false);
-      assertThat(underTest.isOperational(ProcessId.WEB_SERVER, true)).isEqualTo(false);
-      assertThat(underTest.isOperational(ProcessId.COMPUTE_ENGINE, true)).isEqualTo(false);
-    }
-  }
-
-  @Test
-  public void registerSonarQubeVersion_publishes_version_on_first_call() {
-    TestAppSettings settings = newClusterSettings();
-
-    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
-      appStateCluster.registerSonarQubeVersion("6.4.1.5");
-
-      HazelcastInstance hzInstance = createHazelcastClient(appStateCluster);
-      assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get())
-        .isNotNull()
-        .isInstanceOf(String.class)
-        .isEqualTo("6.4.1.5");
-    }
-  }
-
-  @Test
-  public void reset_throws_always_ISE() {
-    TestAppSettings settings = newClusterSettings();
-
-    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
-      expectedException.expect(IllegalStateException.class);
-      expectedException.expectMessage("state reset is not supported in cluster mode");
-      appStateCluster.reset();
-    }
-  }
-
-  @Test
-  public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() throws Exception {
-    // Now launch an instance that try to be part of the hzInstance cluster
-    TestAppSettings settings = newClusterSettings();
-
-    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
-      // Register first version
-      appStateCluster.registerSonarQubeVersion("1.0.0");
-
-      expectedException.expect(IllegalStateException.class);
-      expectedException.expectMessage("The local version 2.0.0 is not the same as the cluster 1.0.0");
-
-      // Registering a second different version must trigger an exception
-      appStateCluster.registerSonarQubeVersion("2.0.0");
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java
deleted file mode 100644 (file)
index 5472d57..0000000
+++ /dev/null
@@ -1,40 +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.application.cluster;
-
-import org.junit.Test;
-import org.sonar.process.ProcessId;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ClusterProcessTest {
-  @Test
-  public void test_equality() {
-    ClusterProcess clusterProcess = new ClusterProcess("A", ProcessId.WEB_SERVER);
-
-    assertThat(clusterProcess)
-      .isNotEqualTo(null)
-      .isEqualTo(clusterProcess)
-      .isNotEqualTo(new ClusterProcess("B", ProcessId.WEB_SERVER))
-      .isNotEqualTo(new ClusterProcess("A", ProcessId.ELASTICSEARCH))
-      .isEqualTo(new ClusterProcess("A", ProcessId.WEB_SERVER));
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java
deleted file mode 100644 (file)
index 05e471c..0000000
+++ /dev/null
@@ -1,122 +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.application.cluster;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.ProcessProperties;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.stream.Stream;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ClusterPropertiesTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private AppSettings appSettings = new TestAppSettings();
-
-  @Test
-  public void test_default_values() throws Exception {
-    ClusterProperties props = new ClusterProperties(appSettings);
-
-    assertThat(props.getNetworkInterfaces())
-      .isEqualTo(Collections.emptyList());
-    assertThat(props.getPort())
-      .isEqualTo(9003);
-    assertThat(props.isEnabled())
-      .isEqualTo(false);
-    assertThat(props.getHosts())
-      .isEqualTo(Collections.emptyList());
-    assertThat(props.getName())
-      .isEqualTo("sonarqube");
-  }
-
-  @Test
-  public void test_port_parameter() {
-    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
-    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
-
-    Stream.of("-50", "0", "65536", "128563").forEach(
-      port -> {
-        appSettings.getProps().set(ProcessProperties.CLUSTER_PORT, port);
-
-        ClusterProperties clusterProperties = new ClusterProperties(appSettings);
-        expectedException.expect(IllegalArgumentException.class);
-        expectedException.expectMessage(
-          String.format("Cluster port have been set to %s which is outside the range [1-65535].", port));
-        clusterProperties.validate();
-
-      });
-  }
-
-  @Test
-  public void test_interfaces_parameter() {
-    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
-    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
-    appSettings.getProps().set(ProcessProperties.CLUSTER_NETWORK_INTERFACES, "8.8.8.8"); // This IP belongs to Google
-
-    ClusterProperties clusterProperties = new ClusterProperties(appSettings);
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage(
-      String.format("Interface %s is not available on this machine.", "8.8.8.8"));
-    clusterProperties.validate();
-  }
-
-  @Test
-  public void validate_does_not_fail_if_cluster_enabled_and_name_specified() {
-    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
-    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
-
-    ClusterProperties clusterProperties = new ClusterProperties(appSettings);
-    clusterProperties.validate();
-  }
-
-  @Test
-  public void test_members() {
-    appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
-    appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "sonarqube");
-
-    assertThat(
-      new ClusterProperties(appSettings).getHosts()).isEqualTo(
-        Collections.emptyList());
-
-    appSettings.getProps().set(ProcessProperties.CLUSTER_HOSTS, "192.168.1.1");
-    assertThat(
-      new ClusterProperties(appSettings).getHosts()).isEqualTo(
-        Arrays.asList("192.168.1.1:9003"));
-
-    appSettings.getProps().set(ProcessProperties.CLUSTER_HOSTS, "192.168.1.2:5501");
-    assertThat(
-      new ClusterProperties(appSettings).getHosts()).containsExactlyInAnyOrder(
-        "192.168.1.2:5501");
-
-    appSettings.getProps().set(ProcessProperties.CLUSTER_HOSTS, "192.168.1.2:5501,192.168.1.1");
-    assertThat(
-      new ClusterProperties(appSettings).getHosts()).containsExactlyInAnyOrder(
-        "192.168.1.2:5501", "192.168.1.1:9003");
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java
deleted file mode 100644 (file)
index 28103e1..0000000
+++ /dev/null
@@ -1,305 +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.application.cluster;
-
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.AppenderBase;
-import com.hazelcast.core.HazelcastInstance;
-import com.hazelcast.core.ItemEvent;
-import com.hazelcast.core.ItemListener;
-import com.hazelcast.core.ReplicatedMap;
-import java.net.InetAddress;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.junit.AfterClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.AppStateListener;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.cluster.ClusterObjectKeys;
-
-import static junit.framework.TestCase.fail;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.sonar.application.cluster.HazelcastTestHelper.closeAllHazelcastClients;
-import static org.sonar.application.cluster.HazelcastTestHelper.createHazelcastClient;
-import static org.sonar.application.cluster.HazelcastTestHelper.newClusterSettings;
-import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterObjectKeys.LEADER;
-import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
-import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
-
-public class HazelcastClusterTest {
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @AfterClass
-  public static void closeHazelcastClients() {
-    closeAllHazelcastClients();
-  }
-
-  @Test
-  public void test_two_tryToLockWebLeader_must_return_true() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(true);
-      assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(false);
-    }
-  }
-
-  @Test
-  public void when_another_process_locked_webleader_tryToLockWebLeader_must_return_false() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
-      hzInstance.getAtomicReference(LEADER).set("aaaa");
-      assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(false);
-    }
-  }
-
-  @Test
-  public void when_no_leader_getLeaderHostName_must_return_NO_LEADER() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.getLeaderHostName()).isEmpty();
-    }
-  }
-
-  @Test
-  public void when_no_leader_getLeaderHostName_must_return_the_hostname() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.tryToLockWebLeader()).isTrue();
-      assertThat(hzCluster.getLeaderHostName().get()).isEqualTo(NetworkUtils.getHostName());
-    }
-  }
-
-  @Test
-  public void members_must_be_empty_when_there_is_no_other_node() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.getMembers()).isEmpty();
-    }
-  }
-
-  @Test
-  public void set_operational_is_writing_to_cluster() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-
-      hzCluster.setOperational(ProcessId.ELASTICSEARCH);
-
-      assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isTrue();
-      assertThat(hzCluster.isOperational(ProcessId.WEB_SERVER)).isFalse();
-      assertThat(hzCluster.isOperational(ProcessId.COMPUTE_ENGINE)).isFalse();
-
-      // Connect via Hazelcast client to test values
-      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
-      ReplicatedMap<ClusterProcess, Boolean> operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
-      assertThat(operationalProcesses)
-        .containsExactly(new AbstractMap.SimpleEntry<>(new ClusterProcess(hzCluster.getLocalUUID(), ProcessId.ELASTICSEARCH), Boolean.TRUE));
-    }
-  }
-
-  @Test
-  public void cluster_name_comes_from_configuration() {
-    TestAppSettings testAppSettings = newClusterSettings();
-    testAppSettings.set(CLUSTER_NAME, "a_cluster_");
-    ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.getName()).isEqualTo("a_cluster_");
-    }
-  }
-
-  @Test
-  public void cluster_must_keep_a_list_of_clients() throws InterruptedException {
-    TestAppSettings testAppSettings = newClusterSettings();
-    testAppSettings.set(CLUSTER_NAME, "a_cluster_");
-    ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS)).isEmpty();
-      HazelcastInstance hzClient = HazelcastTestHelper.createHazelcastClient(hzCluster);
-      assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS)).containsExactly(hzClient.getLocalEndpoint().getUuid());
-
-      CountDownLatch latch = new CountDownLatch(1);
-      hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS).addItemListener(new ItemListener<Object>() {
-        @Override
-        public void itemAdded(ItemEvent<Object> item) {
-        }
-
-        @Override
-        public void itemRemoved(ItemEvent<Object> item) {
-          latch.countDown();
-        }
-      }, false);
-
-      hzClient.shutdown();
-      if (latch.await(5, TimeUnit.SECONDS)) {
-        assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.CLIENT_UUIDS).size()).isEqualTo(0);
-      } else {
-        fail("The client UUID have not been removed from the Set within 5 seconds' time lapse");
-      }
-    }
-  }
-
-  @Test
-  public void localUUID_must_not_be_empty() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.getLocalUUID()).isNotEmpty();
-    }
-  }
-
-  @Test
-  public void when_a_process_is_set_operational_listener_must_be_triggered() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      AppStateListener listener = mock(AppStateListener.class);
-      hzCluster.addListener(listener);
-
-      // ElasticSearch is not operational
-      assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isFalse();
-
-      // Simulate a node that set ElasticSearch operational
-      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
-      ReplicatedMap<ClusterProcess, Boolean> operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
-      operationalProcesses.put(new ClusterProcess(UUID.randomUUID().toString(), ProcessId.ELASTICSEARCH), Boolean.TRUE);
-      verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
-      verifyNoMoreInteractions(listener);
-
-      // ElasticSearch is operational
-      assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isTrue();
-    }
-  }
-
-
-  @Test
-  public void registerSonarQubeVersion_publishes_version_on_first_call() {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      hzCluster.registerSonarQubeVersion("1.0.0.0");
-
-      HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
-      assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get()).isEqualTo("1.0.0.0");
-    }
-  }
-
-  @Test
-  public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() throws Exception {
-    ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      // Register first version
-      hzCluster.registerSonarQubeVersion("1.0.0");
-
-      expectedException.expect(IllegalStateException.class);
-      expectedException.expectMessage("The local version 2.0.0 is not the same as the cluster 1.0.0");
-
-      // Registering a second different version must trigger an exception
-      hzCluster.registerSonarQubeVersion("2.0.0");
-    }
-  }
-
-  @Test
-  public void simulate_network_cluster() throws InterruptedException {
-    TestAppSettings settings = newClusterSettings();
-    settings.set(ProcessProperties.CLUSTER_NETWORK_INTERFACES, InetAddress.getLoopbackAddress().getHostAddress());
-    AppStateListener listener = mock(AppStateListener.class);
-
-    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
-      appStateCluster.addListener(listener);
-
-      HazelcastInstance hzInstance = createHazelcastClient(appStateCluster.getHazelcastCluster());
-      String uuid = UUID.randomUUID().toString();
-      ReplicatedMap<ClusterProcess, Boolean> replicatedMap = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
-      // process is not up yet --> no events are sent to listeners
-      replicatedMap.put(
-        new ClusterProcess(uuid, ProcessId.ELASTICSEARCH),
-        Boolean.FALSE);
-
-      // process is up yet --> notify listeners
-      replicatedMap.replace(
-        new ClusterProcess(uuid, ProcessId.ELASTICSEARCH),
-        Boolean.TRUE);
-
-      // should be called only once
-      verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
-      verifyNoMoreInteractions(listener);
-
-      hzInstance.shutdown();
-    }
-  }
-
-  @Test
-  public void hazelcast_must_log_through_sl4fj() {
-    MemoryAppender<ILoggingEvent> memoryAppender = new MemoryAppender<>();
-    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-    lc.reset();
-    memoryAppender.setContext(lc);
-    memoryAppender.start();
-    lc.getLogger("com.hazelcast").addAppender(memoryAppender);
-
-    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(newClusterSettings())) {
-    }
-
-    assertThat(memoryAppender.events).isNotEmpty();
-    memoryAppender.events.stream().forEach(
-      e -> assertThat(e.getLoggerName()).startsWith("com.hazelcast")
-    );
-  }
-
-  private class MemoryAppender<E> extends AppenderBase<E> {
-    private final List<E> events = new ArrayList();
-
-    @Override
-    protected void append(E eventObject) {
-      events.add(eventObject);
-    }
-  }
-
-
-  @Test
-  public void configuration_tweaks_of_hazelcast_must_be_present() {
-    try (HazelcastCluster hzCluster = HazelcastCluster.create(new ClusterProperties(newClusterSettings()))) {
-      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.tcp.join.port.try.count")).isEqualTo("10");
-      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.phone.home.enabled")).isEqualTo("false");
-      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.logging.type")).isEqualTo("slf4j");
-      assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.socket.bind.any")).isEqualTo("false");
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java
deleted file mode 100644 (file)
index 76724b3..0000000
+++ /dev/null
@@ -1,74 +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.application.cluster;
-
-import com.hazelcast.client.HazelcastClient;
-import com.hazelcast.client.config.ClientConfig;
-import com.hazelcast.core.HazelcastInstance;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.List;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.ProcessProperties;
-
-public class HazelcastTestHelper {
-
-  // Be careful this test won't work if parallel tests is used
-  private static final List<HazelcastInstance> HAZELCAST_INSTANCES = new ArrayList<>();
-
-  static HazelcastInstance createHazelcastClient(HazelcastCluster hzCluster) {
-    ClientConfig clientConfig = new ClientConfig();
-    InetSocketAddress socketAddress = (InetSocketAddress) hzCluster.hzInstance.getLocalEndpoint().getSocketAddress();
-
-    clientConfig.getNetworkConfig().getAddresses().add(
-      String.format("%s:%d",
-        socketAddress.getHostString(),
-        socketAddress.getPort()
-      ));
-    clientConfig.getGroupConfig().setName(hzCluster.getName());
-    HazelcastInstance hazelcastInstance = HazelcastClient.newHazelcastClient(clientConfig);
-    HAZELCAST_INSTANCES.add(hazelcastInstance);
-    return hazelcastInstance;
-  }
-
-  static void closeAllHazelcastClients() {
-    HAZELCAST_INSTANCES.stream().forEach(
-        hz -> {
-          try {
-            hz.shutdown();
-          } catch (Exception ex) {
-            // Ignore it
-          }
-        }
-    );
-  }
-
-  static HazelcastInstance createHazelcastClient(AppStateClusterImpl appStateCluster) {
-    return createHazelcastClient(appStateCluster.getHazelcastCluster());
-  }
-
-  static TestAppSettings newClusterSettings() {
-    TestAppSettings settings = new TestAppSettings();
-    settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_NAME, "sonarqube");
-    return settings;
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsImplTest.java
deleted file mode 100644 (file)
index 2ea6215..0000000
+++ /dev/null
@@ -1,45 +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.application.config;
-
-import java.util.Properties;
-import org.junit.Test;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class AppSettingsImplTest {
-
-  @Test
-  public void reload_updates_properties() {
-    Props initialProps = new Props(new Properties());
-    initialProps.set("foo", "bar");
-    Props newProps = new Props(new Properties());
-    newProps.set("foo", "baz");
-    newProps.set("newProp", "newVal");
-
-    AppSettingsImpl underTest = new AppSettingsImpl(initialProps);
-    underTest.reload(newProps);
-
-    assertThat(underTest.getValue("foo").get()).isEqualTo("baz");
-    assertThat(underTest.getValue("newProp").get()).isEqualTo("newVal");
-    assertThat(underTest.getProps().rawProperties()).hasSize(2);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java
deleted file mode 100644 (file)
index d1337a8..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.application.config;
-
-import java.io.File;
-import org.apache.commons.io.FileUtils;
-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.assertj.core.data.MapEntry.entry;
-
-public class AppSettingsLoaderImplTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Test
-  public void load_properties_from_file() throws Exception {
-    File homeDir = temp.newFolder();
-    File propsFile = new File(homeDir, "conf/sonar.properties");
-    FileUtils.write(propsFile, "foo=bar");
-
-    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[0], homeDir);
-    AppSettings settings = underTest.load();
-
-    assertThat(settings.getProps().rawProperties()).contains(entry("foo", "bar"));
-  }
-
-  @Test
-  public void throws_ISE_if_file_fails_to_be_loaded() throws Exception {
-    File homeDir = temp.newFolder();
-    File propsFileAsDir = new File(homeDir, "conf/sonar.properties");
-    FileUtils.forceMkdir(propsFileAsDir);
-    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[0], homeDir);
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Cannot open file " + propsFileAsDir.getAbsolutePath());
-
-    underTest.load();
-  }
-
-  @Test
-  public void file_is_not_loaded_if_it_does_not_exist() throws Exception {
-    File homeDir = temp.newFolder();
-
-    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[0], homeDir);
-    AppSettings settings = underTest.load();
-
-    // no failure, file is ignored
-    assertThat(settings.getProps()).isNotNull();
-  }
-
-  @Test
-  public void command_line_arguments_are_included_to_settings() throws Exception {
-    File homeDir = temp.newFolder();
-
-    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[] {"-Dsonar.foo=bar", "-Dhello=world"}, homeDir);
-    AppSettings settings = underTest.load();
-
-    assertThat(settings.getProps().rawProperties())
-      .contains(entry("sonar.foo", "bar"))
-      .contains(entry("hello", "world"));
-  }
-
-  @Test
-  public void command_line_arguments_make_precedence_over_properties_files() throws Exception {
-    File homeDir = temp.newFolder();
-    File propsFile = new File(homeDir, "conf/sonar.properties");
-    FileUtils.write(propsFile, "sonar.foo=file");
-
-    AppSettingsLoaderImpl underTest = new AppSettingsLoaderImpl(new String[]{"-Dsonar.foo=cli"}, homeDir);
-    AppSettings settings = underTest.load();
-
-    assertThat(settings.getProps().rawProperties()).contains(entry("sonar.foo", "cli"));
-  }
-
-  @Test
-  public void detectHomeDir_returns_existing_dir() throws Exception {
-    assertThat(new AppSettingsLoaderImpl(new String[0]).getHomeDir()).exists().isDirectory();
-
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsLoopbackTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsLoopbackTest.java
deleted file mode 100644 (file)
index de28db4..0000000
+++ /dev/null
@@ -1,225 +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.application.config;
-
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.Collections;
-import java.util.Enumeration;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.FromDataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.sonar.process.MessageException;
-
-import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
-import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
-import static org.sonar.process.ProcessProperties.CLUSTER_NETWORK_INTERFACES;
-import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
-import static org.sonar.process.ProcessProperties.JDBC_URL;
-import static org.sonar.process.ProcessProperties.SEARCH_HOST;
-
-@RunWith(Theories.class)
-public class ClusterSettingsLoopbackTest {
-
-  private TestAppSettings settings;
-  private static final String LOOPBACK_FORBIDDEN = " must not be a loopback address";
-  private static final String NOT_LOCAL_ADDRESS = " is not a local address";
-  private static final String NOT_RESOLVABLE = " cannot be resolved";
-
-  @DataPoints("parameter")
-  public static final ValueAndResult[] VALID_SINGLE_IP = {
-      // Valid IPs
-      new ValueAndResult("1.2.3.4", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("1.2.3.4:9001", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
-
-      // Valid Name
-      new ValueAndResult("www.sonarqube.org", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("www.google.fr", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("www.google.com, www.sonarsource.com, wwww.sonarqube.org", NOT_LOCAL_ADDRESS),
-
-      new ValueAndResult("...", NOT_RESOLVABLE),
-      new ValueAndResult("භඦආ\uD801\uDC8C\uD801\uDC8B", NOT_RESOLVABLE),
-
-      // Valide IPs List
-      new ValueAndResult("1.2.3.4,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("1.2.3.4:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,1.2.3.4:9001", NOT_LOCAL_ADDRESS),
-      new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccc", NOT_LOCAL_ADDRESS),
-
-      // Loopback IPs
-      new ValueAndResult("localhost", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.0.0.1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.1.1.1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.243.136.241", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("::1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
-
-      // Loopback IPs list
-      new ValueAndResult("127.0.0.1,192.168.11.25", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("192.168.11.25,127.1.1.1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("0:0:0:0:0:0:0:1,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,::1", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("127.0.0.1,192.168.11.25:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("192.168.11.25:9001,127.1.1.1:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[0:0:0:0:0:0:0:1]:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[::1]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
-      new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN)
-  };
-
-  @DataPoints("key")
-  public static final Key[] KEYS = {
-    new Key(SEARCH_HOST, false, false),
-    new Key(CLUSTER_NETWORK_INTERFACES, true, false),
-    new Key(CLUSTER_SEARCH_HOSTS, true, true),
-    new Key(CLUSTER_HOSTS, true, true)
-  };
-
-
-  @DataPoints("unresolvable_hosts")
-  public static final String[] UNRESOLVABLE_HOSTS = {
-  };
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Before
-  public void resetSettings() {
-    settings = getClusterSettings();
-  }
-
-  @Theory
-  public void accept_throws_MessageException(
-    @FromDataPoints("key") Key propertyKey,
-    @FromDataPoints("parameter") ValueAndResult valueAndResult) {
-    // Skip the test if the value is a list and if the key is not accepting a list
-    if (settings == null) {
-      System.out.println("No network found, skipping the test");
-      return;
-    }
-    if ((valueAndResult.isList() && propertyKey.acceptList) || !valueAndResult.isList()) {
-      settings.set(propertyKey.getKey(), valueAndResult.getValue());
-
-      // If the key accept non local IPs there won't be any exception
-      if (!propertyKey.acceptNonLocal || valueAndResult.getMessage() != NOT_LOCAL_ADDRESS) {
-        expectedException.expect(MessageException.class);
-        expectedException.expectMessage(valueAndResult.getMessage());
-      }
-
-      new ClusterSettings().accept(settings.getProps());
-    }
-  }
-
-  private static TestAppSettings getClusterSettings()  {
-    String localAddress = null;
-    try {
-      Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
-      for (NetworkInterface networkInterface : Collections.list(nets)) {
-        if (!networkInterface.isLoopback() && networkInterface.isUp()) {
-          localAddress = networkInterface.getInetAddresses().nextElement().getHostAddress();
-        }
-      }
-      if (localAddress == null) {
-        return null;
-      }
-
-    } catch (SocketException e) {
-      return null;
-    }
-
-    TestAppSettings testAppSettings = new TestAppSettings()
-      .set(CLUSTER_ENABLED, "true")
-      .set(CLUSTER_SEARCH_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
-      .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
-      .set(SEARCH_HOST, localAddress)
-      .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
-    return testAppSettings;
-  }
-
-  private static class Key {
-    private final String key;
-    private final boolean acceptList;
-    private final boolean acceptNonLocal;
-
-    private Key(String key, boolean acceptList, boolean acceptNonLocal) {
-      this.key = key;
-      this.acceptList = acceptList;
-      this.acceptNonLocal = acceptNonLocal;
-    }
-
-    public String getKey() {
-      return key;
-    }
-
-    public boolean acceptList() {
-      return acceptList;
-    }
-
-    public boolean acceptNonLocal() {
-      return acceptNonLocal;
-    }
-  }
-
-  private static class ValueAndResult {
-    private final String value;
-    private final String message;
-
-    private ValueAndResult(String value, String message) {
-      this.value = value;
-      this.message = message;
-    }
-
-    public String getValue() {
-      return value;
-    }
-
-    public String getMessage() {
-      return message;
-    }
-
-    public boolean isList() {
-      return value != null && value.contains(",");
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
deleted file mode 100644 (file)
index ed89b46..0000000
+++ /dev/null
@@ -1,222 +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.application.config;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.process.MessageException;
-import org.sonar.process.ProcessProperties;
-
-import static java.lang.String.format;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
-import static org.sonar.process.ProcessId.ELASTICSEARCH;
-import static org.sonar.process.ProcessId.WEB_SERVER;
-import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
-import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
-import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_DISABLED;
-import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
-import static org.sonar.process.ProcessProperties.JDBC_URL;
-import static org.sonar.process.ProcessProperties.SEARCH_HOST;
-
-
-public class ClusterSettingsTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private TestAppSettings settings;
-
-  @Before
-  public void resetSettings() {
-    settings = getClusterSettings();
-  }
-
-  @Test
-  public void test_isClusterEnabled() {
-    settings.set(CLUSTER_ENABLED, "true");
-    assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue();
-
-    settings.set(CLUSTER_ENABLED, "false");
-    assertThat(ClusterSettings.isClusterEnabled(settings)).isFalse();
-  }
-
-  @Test
-  public void isClusterEnabled_returns_false_by_default() {
-    assertThat(ClusterSettings.isClusterEnabled(new TestAppSettings())).isFalse();
-  }
-
-  @Test
-  public void getEnabledProcesses_returns_all_processes_by_default() {
-    assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, ELASTICSEARCH, WEB_SERVER);
-  }
-
-  @Test
-  public void getEnabledProcesses_returns_all_processes_by_default_in_cluster_mode() {
-    settings.set(CLUSTER_ENABLED, "true");
-
-    assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, ELASTICSEARCH, WEB_SERVER);
-  }
-
-  @Test
-  public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() {
-    settings.set(CLUSTER_ENABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
-
-    assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, WEB_SERVER);
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_internal_property_for_web_leader_is_configured() {
-    settings.set(CLUSTER_ENABLED, "true");
-    settings.set("sonar.cluster.web.startupLeader", "true");
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Property [sonar.cluster.web.startupLeader] is forbidden");
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_search_enabled_with_loopback() {
-    settings.set(CLUSTER_ENABLED, "true");
-    settings.set(CLUSTER_SEARCH_DISABLED, "false");
-    settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2");
-    settings.set(SEARCH_HOST, "::1");
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("The interface address [::1] of [sonar.search.host] must not be a loopback address");
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  @Test
-  public void accept_not_throwing_MessageException_if_search_disabled_with_loopback() {
-    settings.set(CLUSTER_ENABLED, "true");
-    settings.set(CLUSTER_SEARCH_DISABLED, "true");
-    settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2");
-    settings.set(SEARCH_HOST, "127.0.0.1");
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  @Test
-  public void accept_does_nothing_if_cluster_is_disabled() {
-    settings.set(CLUSTER_ENABLED, "false");
-    // this property is supposed to fail if cluster is enabled
-    settings.set("sonar.cluster.web.startupLeader", "true");
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_h2() {
-    settings.set(CLUSTER_ENABLED, "true");
-    settings.set("sonar.jdbc.url", "jdbc:h2:mem");
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Embedded database is not supported in cluster mode");
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_default_jdbc_url() {
-    settings.clearProperty(JDBC_URL);
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Embedded database is not supported in cluster mode");
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  @Test
-  public void isLocalElasticsearchEnabled_returns_true_by_default() {
-    assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
-  }
-
-  @Test
-  public void isLocalElasticsearchEnabled_returns_true_by_default_in_cluster_mode() {
-    assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
-  }
-
-  @Test
-  public void isLocalElasticsearchEnabled_returns_false_if_local_es_node_is_disabled_in_cluster_mode() {
-    settings.set(CLUSTER_ENABLED, "true");
-    settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
-
-    assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse();
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_searchHost_is_missing() {
-    settings.clearProperty(SEARCH_HOST);
-    checkMandatoryProperty(SEARCH_HOST);
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_searchHost_is_blank() {
-    settings.set(SEARCH_HOST, " ");
-    checkMandatoryProperty(SEARCH_HOST);
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_clusterHosts_is_missing() {
-    settings.clearProperty(CLUSTER_HOSTS);
-    checkMandatoryProperty(CLUSTER_HOSTS);
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_clusterHosts_is_blank() {
-    settings.set(CLUSTER_HOSTS, " ");
-    checkMandatoryProperty(CLUSTER_HOSTS);
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() {
-    settings.clearProperty(CLUSTER_SEARCH_HOSTS);
-    checkMandatoryProperty(CLUSTER_SEARCH_HOSTS);
-  }
-
-  @Test
-  public void accept_throws_MessageException_if_clusterSearchHosts_is_blank() {
-    settings.set(CLUSTER_SEARCH_HOSTS, " ");
-    checkMandatoryProperty(CLUSTER_SEARCH_HOSTS);
-  }
-
-  private void checkMandatoryProperty(String key) {
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage(format("Property [%s] is mandatory", key));
-
-    new ClusterSettings().accept(settings.getProps());
-  }
-
-  private static TestAppSettings getClusterSettings() {
-    TestAppSettings testAppSettings = new TestAppSettings()
-      .set(CLUSTER_ENABLED, "true")
-      .set(CLUSTER_SEARCH_HOSTS, "localhost")
-      .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
-      .set(SEARCH_HOST, "192.168.233.1")
-      .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
-    return testAppSettings;
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/CommandLineParserTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/CommandLineParserTest.java
deleted file mode 100644 (file)
index d94851e..0000000
+++ /dev/null
@@ -1,61 +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.application.config;
-
-import java.util.Properties;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class CommandLineParserTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Test
-  public void parseArguments() {
-    System.setProperty("CommandLineParserTest.unused", "unused");
-    System.setProperty("sonar.CommandLineParserTest.used", "used");
-
-    Properties p = CommandLineParser.parseArguments(new String[] {"-Dsonar.foo=bar"});
-
-    // test environment can already declare some system properties prefixed by "sonar."
-    // so we can't test the exact number "2"
-    assertThat(p.size()).isGreaterThanOrEqualTo(2);
-    assertThat(p.getProperty("sonar.foo")).isEqualTo("bar");
-    assertThat(p.getProperty("sonar.CommandLineParserTest.used")).isEqualTo("used");
-
-  }
-
-  @Test
-  public void argumentsToProperties_throws_IAE_if_argument_does_not_start_with_minusD() {
-    Properties p = CommandLineParser.argumentsToProperties(new String[] {"-Dsonar.foo=bar", "-Dsonar.whitespace=foo bar"});
-    assertThat(p).hasSize(2);
-    assertThat(p.getProperty("sonar.foo")).isEqualTo("bar");
-    assertThat(p.getProperty("sonar.whitespace")).isEqualTo("foo bar");
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Command-line argument must start with -D, for example -Dsonar.jdbc.username=sonar. Got: sonar.bad=true");
-
-    CommandLineParser.argumentsToProperties(new String[] {"-Dsonar.foo=bar", "sonar.bad=true"});
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java
deleted file mode 100644 (file)
index dd902f0..0000000
+++ /dev/null
@@ -1,75 +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.application.config;
-
-import java.io.File;
-import java.util.Properties;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.ProcessProperties.PATH_DATA;
-import static org.sonar.process.ProcessProperties.PATH_HOME;
-import static org.sonar.process.ProcessProperties.PATH_LOGS;
-import static org.sonar.process.ProcessProperties.PATH_TEMP;
-import static org.sonar.process.ProcessProperties.PATH_WEB;
-
-
-public class FileSystemSettingsTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private FileSystemSettings underTest = new FileSystemSettings();
-  private File homeDir;
-
-  @Before
-  public void setUp() throws Exception {
-    homeDir = temp.newFolder();
-  }
-
-  @Test
-  public void relative_paths_are_converted_to_absolute_paths() throws Exception {
-    Props props = new Props(new Properties());
-    props.set(PATH_HOME, homeDir.getAbsolutePath());
-
-    // relative paths
-    props.set(PATH_DATA, "data");
-    props.set(PATH_LOGS, "logs");
-    props.set(PATH_TEMP, "temp");
-
-    // already absolute paths
-    props.set(PATH_WEB, new File(homeDir, "web").getAbsolutePath());
-
-    underTest.accept(props);
-
-    assertThat(props.nonNullValue(PATH_DATA)).isEqualTo(new File(homeDir, "data").getAbsolutePath());
-    assertThat(props.nonNullValue(PATH_LOGS)).isEqualTo(new File(homeDir, "logs").getAbsolutePath());
-    assertThat(props.nonNullValue(PATH_TEMP)).isEqualTo(new File(homeDir, "temp").getAbsolutePath());
-    assertThat(props.nonNullValue(PATH_WEB)).isEqualTo(new File(homeDir, "web").getAbsolutePath());
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/JdbcSettingsTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/JdbcSettingsTest.java
deleted file mode 100644 (file)
index 5b4eb94..0000000
+++ /dev/null
@@ -1,221 +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.application.config;
-
-import java.io.File;
-import java.net.InetAddress;
-import java.util.Properties;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.process.MessageException;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.application.config.JdbcSettings.Provider;
-import static org.sonar.process.ProcessProperties.JDBC_URL;
-
-public class JdbcSettingsTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  private JdbcSettings underTest = new JdbcSettings();
-  private File homeDir;
-
-  @Before
-  public void setUp() throws Exception {
-    homeDir = temp.newFolder();
-  }
-
-  @Test
-  public void resolve_H2_provider_when_props_is_empty_and_set_URL_to_default_H2() {
-    Props props = newProps();
-
-    assertThat(underTest.resolveProviderAndEnforceNonnullJdbcUrl(props))
-      .isEqualTo(Provider.H2);
-    assertThat(props.nonNullValue(JDBC_URL)).isEqualTo(String.format("jdbc:h2:tcp://%s:9092/sonar", InetAddress.getLoopbackAddress().getHostAddress()));
-  }
-
-  @Test
-  public void resolve_Oracle_when_jdbc_url_contains_oracle_in_any_case() {
-    checkProviderForUrlAndUnchangedUrl("jdbc:oracle:foo", Provider.ORACLE);
-    checkProviderForUrlAndUnchangedUrl("jdbc:OrAcLe:foo", Provider.ORACLE);
-  }
-
-  @Test
-  public void resolve_MySql_when_jdbc_url_contains_mysql_in_any_case() {
-    checkProviderForUrlAndUnchangedUrl("jdbc:mysql:foo", Provider.MYSQL);
-
-    checkProviderForUrlAndUnchangedUrl("jdbc:mYsQL:foo", Provider.MYSQL);
-  }
-
-  @Test
-  public void resolve_SqlServer_when_jdbc_url_contains_sqlserver_in_any_case() {
-    checkProviderForUrlAndUnchangedUrl("jdbc:sqlserver:foo", Provider.SQLSERVER);
-
-    checkProviderForUrlAndUnchangedUrl("jdbc:SQLSeRVeR:foo", Provider.SQLSERVER);
-  }
-
-  @Test
-  public void resolve_POSTGRESQL_when_jdbc_url_contains_POSTGRESQL_in_any_case() {
-    checkProviderForUrlAndUnchangedUrl("jdbc:postgresql:foo", Provider.POSTGRESQL);
-
-    checkProviderForUrlAndUnchangedUrl("jdbc:POSTGRESQL:foo", Provider.POSTGRESQL);
-  }
-
-  private void checkProviderForUrlAndUnchangedUrl(String url, Provider expected) {
-    Props props = newProps(JDBC_URL, url);
-
-    assertThat(underTest.resolveProviderAndEnforceNonnullJdbcUrl(props)).isEqualTo(expected);
-    assertThat(props.nonNullValue(JDBC_URL)).isEqualTo(url);
-  }
-
-  @Test
-  public void fail_with_MessageException_when_provider_is_not_supported() {
-    Props props = newProps(JDBC_URL, "jdbc:microsoft:sqlserver://localhost");
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Unsupported JDBC driver provider: microsoft");
-
-    underTest.resolveProviderAndEnforceNonnullJdbcUrl(props);
-  }
-
-  @Test
-  public void fail_with_MessageException_when_url_does_not_have_jdbc_prefix() {
-    Props props = newProps(JDBC_URL, "oracle:thin:@localhost/XE");
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Bad format of JDBC URL: oracle:thin:@localhost/XE");
-
-    underTest.resolveProviderAndEnforceNonnullJdbcUrl(props);
-  }
-
-  @Test
-  public void check_mysql_parameters() {
-    // minimal -> ok
-    underTest.checkUrlParameters(Provider.MYSQL,
-      "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8");
-
-    // full -> ok
-    underTest.checkUrlParameters(Provider.MYSQL,
-      "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
-
-    // missing required -> ko
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("JDBC URL must have the property 'useUnicode=true'");
-
-    underTest.checkUrlParameters(Provider.MYSQL, "jdbc:mysql://localhost:3306/sonar?characterEncoding=utf8");
-  }
-
-  @Test
-  public void checkAndComplete_sets_driver_path_for_oracle() throws Exception {
-    File driverFile = new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar");
-    FileUtils.touch(driverFile);
-
-    Props props = newProps(JDBC_URL, "jdbc:oracle:thin:@localhost/XE");
-    underTest.accept(props);
-    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
-  }
-
-  @Test
-  public void sets_driver_path_for_h2() throws Exception {
-    File driverFile = new File(homeDir, "lib/jdbc/h2/h2.jar");
-    FileUtils.touch(driverFile);
-
-    Props props = newProps(JDBC_URL, "jdbc:h2:tcp://localhost:9092/sonar");
-    underTest.accept(props);
-    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
-  }
-
-  @Test
-  public void checkAndComplete_sets_driver_path_for_postgresql() throws Exception {
-    File driverFile = new File(homeDir, "lib/jdbc/postgresql/pg.jar");
-    FileUtils.touch(driverFile);
-
-    Props props = newProps(JDBC_URL, "jdbc:postgresql://localhost/sonar");
-    underTest.accept(props);
-    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
-  }
-
-  @Test
-  public void checkAndComplete_sets_driver_path_for_mssql() throws Exception {
-    File driverFile = new File(homeDir, "lib/jdbc/mssql/sqljdbc4.jar");
-    FileUtils.touch(driverFile);
-
-    Props props = newProps(JDBC_URL, "jdbc:sqlserver://localhost/sonar;SelectMethod=Cursor");
-    underTest.accept(props);
-    assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
-  }
-
-  @Test
-  public void driver_file() throws Exception {
-    File driverFile = new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar");
-    FileUtils.touch(driverFile);
-
-    String path = underTest.driverPath(homeDir, Provider.ORACLE);
-    assertThat(path).isEqualTo(driverFile.getAbsolutePath());
-  }
-
-  @Test
-  public void driver_dir_does_not_exist() throws Exception {
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Directory does not exist: extensions/jdbc-driver/oracle");
-
-    underTest.driverPath(homeDir, Provider.ORACLE);
-  }
-
-  @Test
-  public void no_files_in_driver_dir() throws Exception {
-    FileUtils.forceMkdir(new File(homeDir, "extensions/jdbc-driver/oracle"));
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Directory does not contain JDBC driver: extensions/jdbc-driver/oracle");
-
-    underTest.driverPath(homeDir, Provider.ORACLE);
-  }
-
-  @Test
-  public void too_many_files_in_driver_dir() throws Exception {
-    FileUtils.touch(new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc5.jar"));
-    FileUtils.touch(new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar"));
-
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Directory must contain only one JAR file: extensions/jdbc-driver/oracle");
-
-    underTest.driverPath(homeDir, Provider.ORACLE);
-  }
-
-  private Props newProps(String... params) {
-    Properties properties = new Properties();
-    for (int i = 0; i < params.length; i++) {
-      properties.setProperty(params[i], params[i + 1]);
-      i++;
-    }
-    properties.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
-    return new Props(properties);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java
deleted file mode 100644 (file)
index 93c1aa0..0000000
+++ /dev/null
@@ -1,40 +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.application.config;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class SonarQubeVersionHelperTest {
-  @Test
-  public void getSonarQubeVersion_must_not_return_an_empty_string() {
-    assertThat(SonarQubeVersionHelper.getSonarqubeVersion()).isNotEmpty();
-  }
-
-  @Test
-  public void getSonarQubeVersion_must_always_return_same_value() {
-    String sonarqubeVersion = SonarQubeVersionHelper.getSonarqubeVersion();
-    for (int i = 0; i < 3; i++) {
-      assertThat(SonarQubeVersionHelper.getSonarqubeVersion()).isEqualTo(sonarqubeVersion);
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/TestAppSettings.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/TestAppSettings.java
deleted file mode 100644 (file)
index 18f063f..0000000
+++ /dev/null
@@ -1,64 +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.application.config;
-
-import java.util.Optional;
-import java.util.Properties;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-
-/**
- * Simple implementation of {@link AppSettings} that loads
- * the default values defined by {@link ProcessProperties}.
- */
-public class TestAppSettings implements AppSettings {
-
-  private Props props;
-
-  public TestAppSettings() {
-    this.props = new Props(new Properties());
-    ProcessProperties.completeDefaults(this.props);
-  }
-
-  public TestAppSettings set(String key, String value) {
-    this.props.set(key, value);
-    return this;
-  }
-
-  @Override
-  public Props getProps() {
-    return props;
-  }
-
-  @Override
-  public Optional<String> getValue(String key) {
-    return Optional.ofNullable(props.value(key));
-  }
-
-  @Override
-  public void reload(Props copy) {
-    this.props = copy;
-  }
-
-  public void clearProperty(String key) {
-    this.props.rawProperties().remove(key);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/LifecycleTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/LifecycleTest.java
deleted file mode 100644 (file)
index 79c9e47..0000000
+++ /dev/null
@@ -1,111 +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.application.process;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.junit.Test;
-import org.sonar.application.process.Lifecycle;
-import org.sonar.application.process.ProcessLifecycleListener;
-import org.sonar.process.ProcessId;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.application.process.Lifecycle.State.INIT;
-import static org.sonar.application.process.Lifecycle.State.STARTED;
-import static org.sonar.application.process.Lifecycle.State.STARTING;
-import static org.sonar.application.process.Lifecycle.State.STOPPING;
-
-public class LifecycleTest {
-
-  @Test
-  public void initial_state_is_INIT() {
-    Lifecycle lifecycle = new Lifecycle(ProcessId.ELASTICSEARCH, Collections.emptyList());
-    assertThat(lifecycle.getState()).isEqualTo(INIT);
-  }
-
-  @Test
-  public void try_to_move_does_not_support_jumping_states() {
-    TestLifeCycleListener listener = new TestLifeCycleListener();
-    Lifecycle lifecycle = new Lifecycle(ProcessId.ELASTICSEARCH, asList(listener));
-    assertThat(lifecycle.getState()).isEqualTo(INIT);
-    assertThat(listener.states).isEmpty();
-
-    assertThat(lifecycle.tryToMoveTo(STARTED)).isFalse();
-    assertThat(lifecycle.getState()).isEqualTo(INIT);
-    assertThat(listener.states).isEmpty();
-
-    assertThat(lifecycle.tryToMoveTo(STARTING)).isTrue();
-    assertThat(lifecycle.getState()).isEqualTo(STARTING);
-    assertThat(listener.states).containsOnly(STARTING);
-  }
-
-  @Test
-  public void no_state_can_not_move_to_itself() {
-    for (Lifecycle.State state : Lifecycle.State.values()) {
-      assertThat(newLifeCycle(state).tryToMoveTo(state)).isFalse();
-    }
-  }
-
-  @Test
-  public void can_move_to_STOPPING_from_STARTING_STARTED_only() {
-    for (Lifecycle.State state : Lifecycle.State.values()) {
-      TestLifeCycleListener listener = new TestLifeCycleListener();
-      boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STOPPING);
-      if (state == STARTING || state == STARTED) {
-        assertThat(tryToMoveTo).as("from state " + state).isTrue();
-        assertThat(listener.states).containsOnly(STOPPING);
-      } else {
-        assertThat(tryToMoveTo).as("from state " + state).isFalse();
-        assertThat(listener.states).isEmpty();
-      }
-    }
-  }
-
-  @Test
-  public void can_move_to_STARTED_from_STARTING_only() {
-    for (Lifecycle.State state : Lifecycle.State.values()) {
-      TestLifeCycleListener listener = new TestLifeCycleListener();
-      boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STARTED);
-      if (state == STARTING) {
-        assertThat(tryToMoveTo).as("from state " + state).isTrue();
-        assertThat(listener.states).containsOnly(STARTED);
-      } else {
-        assertThat(tryToMoveTo).as("from state " + state).isFalse();
-        assertThat(listener.states).isEmpty();
-      }
-    }
-  }
-
-  private static Lifecycle newLifeCycle(Lifecycle.State state, TestLifeCycleListener... listeners) {
-    return new Lifecycle(ProcessId.ELASTICSEARCH, Arrays.asList(listeners), state);
-  }
-
-  private static final class TestLifeCycleListener implements ProcessLifecycleListener {
-    private final List<Lifecycle.State> states = new ArrayList<>();
-
-    @Override
-    public void onProcessState(ProcessId processId, Lifecycle.State state) {
-      this.states.add(state);
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java
deleted file mode 100644 (file)
index 66c60fc..0000000
+++ /dev/null
@@ -1,111 +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.application.process;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.process.sharedmemoryfile.ProcessCommands;
-import org.sonar.process.ProcessId;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-
-public class ProcessCommandsProcessMonitorTest {
-
-  @Test
-  public void ProcessMonitorImpl_is_a_proxy_of_Process() throws Exception {
-    Process process = mock(Process.class, RETURNS_DEEP_STUBS);
-    ProcessCommands commands = mock(ProcessCommands.class, RETURNS_DEEP_STUBS);
-
-    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, ProcessId.WEB_SERVER, commands);
-
-    underTest.waitFor();
-    verify(process).waitFor();
-
-    underTest.closeStreams();
-    verify(process.getErrorStream()).close();
-    verify(process.getInputStream()).close();
-    verify(process.getOutputStream()).close();
-
-    underTest.destroyForcibly();
-    verify(process).destroyForcibly();
-
-    assertThat(underTest.getInputStream()).isNotNull();
-
-    underTest.isAlive();
-    verify(process).isAlive();
-
-    underTest.waitFor(123, TimeUnit.MILLISECONDS);
-    verify(process).waitFor(123, TimeUnit.MILLISECONDS);
-  }
-
-  @Test
-  public void ProcessMonitorImpl_is_a_proxy_of_Commands() throws Exception {
-    Process process = mock(Process.class, RETURNS_DEEP_STUBS);
-    ProcessCommands commands = mock(ProcessCommands.class, RETURNS_DEEP_STUBS);
-
-    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, null, commands);
-
-    underTest.askForStop();
-    verify(commands).askForStop();
-
-    underTest.acknowledgeAskForRestart();
-    verify(commands).acknowledgeAskForRestart();
-
-    underTest.askedForRestart();
-    verify(commands).askedForRestart();
-
-    underTest.isOperational();
-    verify(commands).isOperational();
-  }
-
-  @Test
-  public void closeStreams_ignores_null_stream() {
-    ProcessCommands commands = mock(ProcessCommands.class);
-    Process process = mock(Process.class);
-    when(process.getInputStream()).thenReturn(null);
-
-    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, null, commands);
-
-    // no failures
-    underTest.closeStreams();
-  }
-
-  @Test
-  public void closeStreams_ignores_failure_if_stream_fails_to_be_closed() throws Exception {
-    InputStream stream = mock(InputStream.class);
-    doThrow(new IOException("error")).when(stream).close();
-    Process process = mock(Process.class);
-    when(process.getInputStream()).thenReturn(stream);
-
-    ProcessCommandsProcessMonitor underTest = new ProcessCommandsProcessMonitor(process, null, mock(ProcessCommands.class, Mockito.RETURNS_MOCKS));
-
-    // no failures
-    underTest.closeStreams();
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
deleted file mode 100644 (file)
index a0a4bba..0000000
+++ /dev/null
@@ -1,168 +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.application.process;
-
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.process.ProcessId;
-import org.sonar.process.command.JavaCommand;
-import org.sonar.process.jmvoptions.JvmOptions;
-import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-import static org.mockito.Mockito.RETURNS_MOCKS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ProcessLauncherImplTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private AllProcessesCommands commands = mock(AllProcessesCommands.class, RETURNS_MOCKS);
-
-  @Test
-  public void launch_forks_a_new_process() throws Exception {
-    File tempDir = temp.newFolder();
-    TestProcessBuilder processBuilder = new TestProcessBuilder();
-    ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
-    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.setJvmOptions(new JvmOptions<>()
-      .add("-Dfoo=bar")
-      .add("-Dfoo2=bar2"));
-
-    ProcessMonitor monitor = underTest.launch(command);
-
-    assertThat(monitor).isNotNull();
-    assertThat(processBuilder.started).isTrue();
-    assertThat(processBuilder.commands.get(0)).endsWith("java");
-    assertThat(processBuilder.commands).containsSequence(
-      "-Dfoo=bar",
-      "-Dfoo2=bar2",
-      "-cp",
-      "lib/*.class" + System.getProperty("path.separator") + "lib/*.jar",
-      "org.sonarqube.Main");
-    assertThat(processBuilder.dir).isEqualTo(command.getWorkDir());
-    assertThat(processBuilder.redirectErrorStream).isTrue();
-    assertThat(processBuilder.environment)
-      .contains(entry("VAR1", "valueOfVar1"))
-      .containsAllEntriesOf(command.getEnvVariables());
-  }
-
-  @Test
-  public void properties_are_passed_to_command_via_a_temporary_properties_file() throws Exception {
-    File tempDir = temp.newFolder();
-    TestProcessBuilder processBuilder = new TestProcessBuilder();
-    ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
-    JavaCommand<JvmOptions> command = new JavaCommand<>(ProcessId.ELASTICSEARCH, temp.newFolder());
-    command.setArgument("foo", "bar");
-    command.setArgument("baz", "woo");
-    command.setJvmOptions(new JvmOptions<>());
-
-    underTest.launch(command);
-
-    String propsFilePath = processBuilder.commands.get(processBuilder.commands.size() - 1);
-    File file = new File(propsFilePath);
-    assertThat(file).exists().isFile();
-    try (FileReader reader = new FileReader(file)) {
-      Properties props = new Properties();
-      props.load(reader);
-      assertThat(props).containsOnly(
-        entry("foo", "bar"),
-        entry("baz", "woo"),
-        entry("process.terminationTimeout", "60000"),
-        entry("process.key", ProcessId.ELASTICSEARCH.getKey()),
-        entry("process.index", String.valueOf(ProcessId.ELASTICSEARCH.getIpcIndex())),
-        entry("process.sharedDir", tempDir.getAbsolutePath()));
-    }
-  }
-
-  @Test
-  public void throw_ISE_if_command_fails() throws IOException {
-    File tempDir = temp.newFolder();
-    ProcessLauncherImpl.ProcessBuilder processBuilder = mock(ProcessLauncherImpl.ProcessBuilder.class, RETURNS_MOCKS);
-    when(processBuilder.start()).thenThrow(new IOException("error"));
-    ProcessLauncher underTest = new ProcessLauncherImpl(tempDir, commands, () -> processBuilder);
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Fail to launch process [es]");
-
-    underTest.launch(new JavaCommand(ProcessId.ELASTICSEARCH, temp.newFolder()));
-  }
-
-  private static class TestProcessBuilder implements ProcessLauncherImpl.ProcessBuilder {
-    private List<String> commands = null;
-    private File dir = null;
-    private Boolean redirectErrorStream = null;
-    private final Map<String, String> environment = new HashMap<>();
-    private boolean started = false;
-
-    @Override
-    public List<String> command() {
-      return commands;
-    }
-
-    @Override
-    public TestProcessBuilder command(List<String> commands) {
-      this.commands = commands;
-      return this;
-    }
-
-    @Override
-    public TestProcessBuilder directory(File dir) {
-      this.dir = dir;
-      return this;
-    }
-
-    @Override
-    public Map<String, String> environment() {
-      return environment;
-    }
-
-    @Override
-    public TestProcessBuilder redirectErrorStream(boolean b) {
-      this.redirectErrorStream = b;
-      return this;
-    }
-
-    @Override
-    public Process start() throws IOException {
-      this.started = true;
-      return mock(Process.class);
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/SQProcessTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/SQProcessTest.java
deleted file mode 100644 (file)
index 004ef44..0000000
+++ /dev/null
@@ -1,333 +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.application.process;
-
-import java.io.InputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.mockito.Mockito;
-import org.sonar.process.ProcessId;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-public class SQProcessTest {
-
-  private static final ProcessId A_PROCESS_ID = ProcessId.ELASTICSEARCH;
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
-  @Test
-  public void initial_state_is_INIT() {
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
-
-    assertThat(underTest.getProcessId()).isEqualTo(A_PROCESS_ID);
-    assertThat(underTest.getState()).isEqualTo(Lifecycle.State.INIT);
-  }
-
-  @Test
-  public void start_and_stop_process() {
-    ProcessLifecycleListener listener = mock(ProcessLifecycleListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addProcessLifecycleListener(listener)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      assertThat(underTest.start(() -> testProcess)).isTrue();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STARTED);
-      assertThat(testProcess.isAlive()).isTrue();
-      assertThat(testProcess.streamsClosed).isFalse();
-      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STARTED);
-
-      testProcess.close();
-      // do not wait next run of watcher threads
-      underTest.refreshState();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
-      assertThat(testProcess.isAlive()).isFalse();
-      assertThat(testProcess.streamsClosed).isTrue();
-      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPED);
-    }
-  }
-
-  @Test
-  public void start_does_not_nothing_if_already_started_once() {
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      assertThat(underTest.start(() -> testProcess)).isTrue();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STARTED);
-
-      assertThat(underTest.start(() -> {throw new IllegalStateException();})).isFalse();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STARTED);
-    }
-  }
-
-  @Test
-  public void start_throws_exception_and_move_to_state_STOPPED_if_execution_of_command_fails() {
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("error");
-
-    underTest.start(() -> {throw new IllegalStateException("error");});
-    assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
-  }
-
-  @Test
-  public void send_event_when_process_is_operational() {
-    ProcessEventListener listener = mock(ProcessEventListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addEventListener(listener)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-
-      testProcess.operational = true;
-      underTest.refreshState();
-
-      verify(listener).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.OPERATIONAL);
-    }
-    verifyNoMoreInteractions(listener);
-  }
-
-  @Test
-  public void operational_event_is_sent_once() {
-    ProcessEventListener listener = mock(ProcessEventListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addEventListener(listener)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-      testProcess.operational = true;
-
-      underTest.refreshState();
-      verify(listener).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.OPERATIONAL);
-
-      // second run
-      underTest.refreshState();
-      verifyNoMoreInteractions(listener);
-    }
-  }
-
-  @Test
-  public void send_event_when_process_requests_for_restart() {
-    ProcessEventListener listener = mock(ProcessEventListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addEventListener(listener)
-      .setWatcherDelayMs(1L)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-
-      testProcess.askedForRestart = true;
-      verify(listener, timeout(10_000)).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.ASK_FOR_RESTART);
-
-      // flag is reset so that next run does not trigger again the event
-      underTest.refreshState();
-      verifyNoMoreInteractions(listener);
-      assertThat(testProcess.askedForRestart).isFalse();
-    }
-  }
-
-  @Test
-  public void stopForcibly_stops_the_process_without_graceful_request_for_stop() {
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-
-      underTest.stopForcibly();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
-      assertThat(testProcess.askedForStop).isFalse();
-      assertThat(testProcess.destroyedForcibly).isTrue();
-
-      // second execution of stopForcibly does nothing. It's still stopped.
-      underTest.stopForcibly();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
-    }
-  }
-
-  @Test
-  public void process_stops_after_graceful_request_for_stop() throws Exception {
-    ProcessLifecycleListener listener = mock(ProcessLifecycleListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addProcessLifecycleListener(listener)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-
-      Thread stopperThread = new Thread(() -> underTest.stop(1, TimeUnit.HOURS));
-      stopperThread.start();
-
-      // thread is blocked until process stopped
-      assertThat(stopperThread.isAlive()).isTrue();
-
-      // wait for the stopper thread to ask graceful stop
-      while (!testProcess.askedForStop) {
-        Thread.sleep(1L);
-      }
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPING);
-      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPING);
-
-      // process stopped
-      testProcess.close();
-
-      // waiting for stopper thread to detect and handle the stop
-      stopperThread.join();
-
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
-      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPED);
-    }
-  }
-
-  @Test
-  public void process_is_stopped_forcibly_if_graceful_stop_is_too_long() throws Exception {
-    ProcessLifecycleListener listener = mock(ProcessLifecycleListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addProcessLifecycleListener(listener)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-
-      underTest.stop(1L, TimeUnit.MILLISECONDS);
-
-      testProcess.waitFor();
-      assertThat(testProcess.askedForStop).isTrue();
-      assertThat(testProcess.destroyedForcibly).isTrue();
-      assertThat(testProcess.isAlive()).isFalse();
-      assertThat(underTest.getState()).isEqualTo(Lifecycle.State.STOPPED);
-      verify(listener).onProcessState(A_PROCESS_ID, Lifecycle.State.STOPPED);
-    }
-  }
-
-  @Test
-  public void process_requests_are_listened_on_regular_basis() throws Exception {
-    ProcessEventListener listener = mock(ProcessEventListener.class);
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID)
-      .addEventListener(listener)
-      .setWatcherDelayMs(1L)
-      .build();
-
-    try (TestProcess testProcess = new TestProcess()) {
-      underTest.start(() -> testProcess);
-
-      testProcess.operational = true;
-
-      verify(listener, timeout(1_000L)).onProcessEvent(A_PROCESS_ID, ProcessEventListener.Type.OPERATIONAL);
-    }
-  }
-
-  @Test
-  public void test_toString() {
-    SQProcess underTest = SQProcess.builder(A_PROCESS_ID).build();
-    assertThat(underTest.toString()).isEqualTo("Process[" + A_PROCESS_ID.getKey() + "]");
-  }
-
-  private static class TestProcess implements ProcessMonitor, AutoCloseable {
-
-    private final CountDownLatch alive = new CountDownLatch(1);
-    private final InputStream inputStream = mock(InputStream.class, Mockito.RETURNS_MOCKS);
-    private final InputStream errorStream = mock(InputStream.class, Mockito.RETURNS_MOCKS);
-    private boolean streamsClosed = false;
-    private boolean operational = false;
-    private boolean askedForRestart = false;
-    private boolean askedForStop = false;
-    private boolean destroyedForcibly = false;
-
-    @Override
-    public InputStream getInputStream() {
-      return inputStream;
-    }
-
-    @Override
-    public InputStream getErrorStream() {
-      return errorStream;
-    }
-
-    @Override
-    public void closeStreams() {
-      streamsClosed = true;
-    }
-
-    @Override
-    public boolean isAlive() {
-      return alive.getCount() == 1;
-    }
-
-    @Override
-    public void askForStop() {
-      askedForStop = true;
-      // do not stop, just asking
-    }
-
-    @Override
-    public void destroyForcibly() {
-      destroyedForcibly = true;
-      alive.countDown();
-    }
-
-    @Override
-    public void waitFor() throws InterruptedException {
-      alive.await();
-    }
-
-    @Override
-    public void waitFor(long timeout, TimeUnit timeoutUnit) throws InterruptedException {
-      alive.await(timeout, timeoutUnit);
-    }
-
-    @Override
-    public boolean isOperational() {
-      return operational;
-    }
-
-    @Override
-    public boolean askedForRestart() {
-      return askedForRestart;
-    }
-
-    @Override
-    public void acknowledgeAskForRestart() {
-      this.askedForRestart = false;
-    }
-
-    @Override
-    public void close() {
-      alive.countDown();
-    }
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
deleted file mode 100644 (file)
index 6d0b972..0000000
+++ /dev/null
@@ -1,114 +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.application.process;
-
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.sonar.application.FileSystem;
-import org.sonar.application.Scheduler;
-import org.sonar.application.config.AppSettings;
-import org.sonar.process.sharedmemoryfile.ProcessCommands;
-import org.sonar.process.ProcessProperties;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-public class StopRequestWatcherImplTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
-  private AppSettings settings = mock(AppSettings.class, RETURNS_DEEP_STUBS);
-  private ProcessCommands commands = mock(ProcessCommands.class);
-  private Scheduler scheduler = mock(Scheduler.class);
-
-  @Test
-  public void do_not_watch_command_if_disabled() throws IOException {
-    enableSetting(false);
-    StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(settings, scheduler, commands);
-
-    underTest.startWatching();
-    assertThat(underTest.isAlive()).isFalse();
-
-    underTest.stopWatching();
-    verifyZeroInteractions(commands, scheduler);
-  }
-
-  @Test
-  public void watch_stop_command_if_enabled() throws Exception {
-    enableSetting(true);
-    StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(settings, scheduler, commands);
-    underTest.setDelayMs(1L);
-
-    underTest.startWatching();
-    assertThat(underTest.isAlive()).isTrue();
-    verify(scheduler, never()).terminate();
-
-    when(commands.askedForStop()).thenReturn(true);
-    verify(scheduler, timeout(1_000L)).terminate();
-
-    underTest.stopWatching();
-    while (underTest.isAlive()) {
-      Thread.sleep(1L);
-    }
-  }
-
-  @Test
-  public void create_instance_with_default_delay() throws IOException {
-    FileSystem fs = mock(FileSystem.class);
-    when(fs.getTempDir()).thenReturn(temp.newFolder());
-
-    StopRequestWatcherImpl underTest = StopRequestWatcherImpl.create(settings, scheduler, fs);
-
-    assertThat(underTest.getDelayMs()).isEqualTo(500L);
-  }
-
-  @Test
-  public void stop_watching_commands_if_thread_is_interrupted() throws Exception {
-    enableSetting(true);
-    StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(settings, scheduler, commands);
-
-    underTest.startWatching();
-    underTest.interrupt();
-
-    while (underTest.isAlive()) {
-      Thread.sleep(1L);
-    }
-    assertThat(underTest.isAlive()).isFalse();
-  }
-
-  private void enableSetting(boolean b) {
-    when(settings.getProps().valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND)).thenReturn(b);
-  }
-
-}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StreamGobblerTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StreamGobblerTest.java
deleted file mode 100644 (file)
index 2f1498d..0000000
+++ /dev/null
@@ -1,51 +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.application.process;
-
-import java.io.InputStream;
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.sonar.application.process.StreamGobbler;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-public class StreamGobblerTest {
-
-  @Test
-  public void forward_stream_to_log() {
-    InputStream stream = IOUtils.toInputStream("one\nsecond log\nthird log\n");
-    Logger logger = mock(Logger.class);
-
-    StreamGobbler gobbler = new StreamGobbler(stream, "WEB", logger);
-    verifyZeroInteractions(logger);
-
-    gobbler.start();
-    StreamGobbler.waitUntilFinish(gobbler);
-
-    verify(logger).info("one");
-    verify(logger).info("second log");
-    verify(logger).info("third log");
-    verifyNoMoreInteractions(logger);
-  }
-}
diff --git a/server/sonar-process-monitor/src/test/resources/logback-test.xml b/server/sonar-process-monitor/src/test/resources/logback-test.xml
deleted file mode 100644 (file)
index 4c62d57..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<configuration debug="false">
-  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
-
-  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-      <pattern>
-        %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
-      </pattern>
-    </encoder>
-  </appender>
-
-  <root>
-    <level value="INFO"/>
-    <appender-ref ref="CONSOLE"/>
-  </root>
-
-</configuration>
index 0baa854c572865b158762dac6bf988a106adc9d4..ec4f84def41d48735f448bf2d826ca089de98a65 100644 (file)
@@ -27,7 +27,7 @@
     </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
-      <artifactId>sonar-process-monitor</artifactId>
+      <artifactId>sonar-main</artifactId>
       <version>${project.version}</version>
     </dependency>
     <!--must declare this dependency of sonar-process-monitor here, again,-->