aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2017-03-10 15:01:01 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2017-03-13 13:54:03 +0100
commit6fa3d925c688fa8e67480c7a69ded9f86aba5326 (patch)
tree11f4baac39111ae6822f945faf344e88b99dec7c
parent857d12fa9909a5b5fde7a42f231b0b8d42e50303 (diff)
downloadsonarqube-6fa3d925c688fa8e67480c7a69ded9f86aba5326.tar.gz
sonarqube-6fa3d925c688fa8e67480c7a69ded9f86aba5326.zip
SONAR-8816 automatic election of web leader in cluster mode
-rw-r--r--it/it-tests/src/test/java/it/serverSystem/ClusterTest.java5
-rw-r--r--pom.xml5
-rw-r--r--server/sonar-process-monitor/pom.xml35
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java (renamed from sonar-application/src/main/java/org/sonar/application/AppFileSystem.java)97
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java (renamed from sonar-application/src/main/java/org/sonar/application/AppLogging.java)38
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloader.java (renamed from server/sonar-ce/src/main/java/org/sonar/ce/app/StartupBarrierFactory.java)23
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloaderImpl.java81
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java (renamed from server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java)49
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateFactory.java (renamed from server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java)22
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java67
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateListener.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java)23
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/FileSystem.java (renamed from server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/FileSystem.java)4
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/NodeLifecycle.java99
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/Scheduler.java (renamed from server/sonar-ce/src/main/java/org/sonar/ce/app/StartupBarrier.java)19
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/SchedulerImpl.java292
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java214
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProcess.java67
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProperties.java (renamed from sonar-application/src/main/java/org/sonar/application/ClusterProperties.java)48
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/package-info.java23
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettings.java32
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsImpl.java (renamed from server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java)33
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoader.java26
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java (renamed from sonar-application/src/main/java/org/sonar/application/PropsBuilder.java)54
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/ClusterSettings.java82
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/CommandLineParser.java (renamed from sonar-application/src/main/java/org/sonar/application/CommandLineParser.java)13
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/FileSystemSettings.java59
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/config/JdbcSettings.java (renamed from sonar-application/src/main/java/org/sonar/application/JdbcSettings.java)41
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/package-info.java (renamed from server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/package-info.java)2
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommand.java (renamed from server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java)7
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactory.java (renamed from sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java)14
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactoryImpl.java124
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncher.java87
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncherImpl.java (renamed from server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java)75
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/Lifecycle.java97
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessEventListener.java43
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java36
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitor.java81
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitorImpl.java103
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/SQProcess.java262
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcher.java32
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java91
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/StreamGobbler.java (renamed from server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java)7
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/package-info.java23
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java547
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java109
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java74
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java197
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/AppLoggingTest.java (renamed from sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java)86
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/AppReloaderImplTest.java110
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java48
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java84
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java443
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java77
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java125
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java136
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastHelper.java91
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsImplTest.java (renamed from server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java)32
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java104
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsTest.java134
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/CommandLineParserTest.java (renamed from sonar-application/src/test/java/org/sonar/application/CommandLineParserTest.java)27
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java75
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/JdbcSettingsTest.java (renamed from sonar-application/src/test/java/org/sonar/application/JdbcSettingsTest.java)71
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/config/TestAppSettings.java60
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaCommandTest.java (renamed from server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java)11
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaProcessLauncherImplTest.java166
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/LifecycleTest.java111
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessMonitorImplTest.java111
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/SQProcessTest.java327
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java114
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/StreamGobblerTest.java (renamed from server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/StreamGobblerTest.java)6
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java630
-rw-r--r--server/sonar-process-monitor/src/test/resources/logback-test.xml (renamed from sonar-application/src/test/resources/logback-test.xml)0
-rw-r--r--server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt1
-rw-r--r--server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt1
-rw-r--r--server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt3
-rw-r--r--server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt1
-rw-r--r--server/sonar-process-monitor/src/test/resources/org/sonar/process/PropsTest/sonar.properties3
-rw-r--r--server/sonar-process-monitor/src/test/resources/sonar-dummy-app.jarbin854048 -> 0 bytes
-rw-r--r--server/sonar-process/pom.xml32
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/FileUtils2.java (renamed from server/sonar-process/src/main/java/org/sonar/process/FileUtils.java)4
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java16
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java2
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java55
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java52
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/FileUtils2Test.java (renamed from server/sonar-process/src/test/java/org/sonar/process/FileUtilsTest.java)30
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java65
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java33
-rw-r--r--server/sonar-process/test-jar-with-dependencies.xml19
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/cluster/Cluster.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterImpl.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java50
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java5
-rw-r--r--sonar-application/pom.xml4
-rw-r--r--sonar-application/src/main/java/org/sonar/application/App.java210
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Cluster.java103
-rw-r--r--sonar-application/src/main/java/org/sonar/application/ClusterParameters.java65
-rw-r--r--sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java114
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java263
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AppTest.java232
-rw-r--r--sonar-application/src/test/java/org/sonar/application/ClusterPropertiesTest.java181
-rw-r--r--sonar-application/src/test/java/org/sonar/application/ClusterTest.java164
-rw-r--r--sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java407
-rw-r--r--sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java99
-rw-r--r--sonar-application/src/test/resources/conf/sonar.properties0
-rw-r--r--tests/upgrade/src/test/java/org/sonarsource/sonarqube/upgrade/UpgradeTest.java2
106 files changed, 5084 insertions, 3884 deletions
diff --git a/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java b/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java
index f2f495a8390..085302104d3 100644
--- a/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java
+++ b/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java
@@ -37,6 +37,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
+import org.junit.Ignore;
import org.junit.Test;
import org.sonarqube.ws.Issues;
import org.sonarqube.ws.Settings;
@@ -48,6 +49,7 @@ import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
import static org.assertj.core.api.Assertions.assertThat;
import static util.ItUtils.newWsClient;
+@Ignore("temporarily ignored")
public class ClusterTest {
private static final String CONF_FILE_PATH = "conf/sonar.properties";
@@ -61,7 +63,6 @@ public class ClusterTest {
Orchestrator orchestrator = Orchestrator.builderEnv()
.setServerProperty("sonar.cluster.enabled", "true")
.setServerProperty("sonar.cluster.name", "secondary_nodes_do_not_write_to_datastores_at_startup")
- .setServerProperty("sonar.cluster.port_autoincrement", "true")
.setServerProperty("sonar.cluster.web.startupLeader", "true")
.setServerProperty("sonar.log.level", "TRACE")
.addPlugin(ItUtils.xooPlugin())
@@ -93,7 +94,6 @@ public class ClusterTest {
elasticsearch = Orchestrator.builderEnv()
.setServerProperty("sonar.cluster.enabled", "true")
.setServerProperty("sonar.cluster.name", "start_cluster_of_elasticsearch_and_web_nodes")
- .setServerProperty("sonar.cluster.port_autoincrement", "true")
.setServerProperty("sonar.cluster.web.disabled", "true")
.setServerProperty("sonar.cluster.ce.disabled", "true")
.setStartupLogWatcher(esWatcher)
@@ -105,7 +105,6 @@ public class ClusterTest {
web = Orchestrator.builderEnv()
.setServerProperty("sonar.cluster.enabled", "true")
.setServerProperty("sonar.cluster.name", "start_cluster_of_elasticsearch_and_web_nodes")
- .setServerProperty("sonar.cluster.port_autoincrement", "true")
.setServerProperty("sonar.cluster.web.startupLeader", "true")
.setServerProperty("sonar.cluster.search.disabled", "true")
.setServerProperty("sonar.cluster.search.hosts", "localhost:" + esWatcher.port)
diff --git a/pom.xml b/pom.xml
index 927509c77e4..bab465ea6b7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -667,6 +667,11 @@
<version>${hazelcast.version}</version>
</dependency>
<dependency>
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast-client</artifactId>
+ <version>${hazelcast.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
diff --git a/server/sonar-process-monitor/pom.xml b/server/sonar-process-monitor/pom.xml
index e7a34123d2e..f4f7bac9633 100644
--- a/server/sonar-process-monitor/pom.xml
+++ b/server/sonar-process-monitor/pom.xml
@@ -1,5 +1,7 @@
<?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">
+<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>
@@ -25,19 +27,12 @@
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
-
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- </dependency>
<dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast</artifactId>
</dependency>
+
+
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
@@ -60,20 +55,8 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sonar-process</artifactId>
- <type>test-jar</type>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.github.kevinsawicki</groupId>
- <artifactId>http-request</artifactId>
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast-client</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
index 9b07583314e..99d22bd26f3 100644
--- a/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
@@ -29,61 +29,37 @@ import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
-import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sonar.application.config.AppSettings;
import org.sonar.process.AllProcessesCommands;
-import org.sonar.process.Props;
-import org.sonar.process.monitor.FileSystem;
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.FileUtils.deleteDirectory;
+import static org.sonar.process.FileUtils2.deleteDirectory;
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 AppFileSystem implements FileSystem {
- private static final Logger LOG = LoggerFactory.getLogger(AppFileSystem.class);
+ private static final Logger LOG = LoggerFactory.getLogger(AppFileSystem.class);
private static final EnumSet<FileVisitOption> FOLLOW_LINKS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
- private static final String DEFAULT_DATA_DIRECTORY_NAME = "data";
- private static final String DEFAULT_WEB_DIRECTORY_NAME = "web";
- private static final String DEFAULT_LOGS_DIRECTORY_NAME = "logs";
- private static final String DEFAULT_TEMP_DIRECTORY_NAME = "temp";
-
- private final Props props;
- private final File homeDir;
- private boolean initialized = false;
-
- public AppFileSystem(Props props) {
- this.props = props;
- this.homeDir = props.nonNullValueAsFile(PATH_HOME);
- }
- public void verifyProps() {
- ensurePropertyIsAbsolutePath(props, PATH_DATA, DEFAULT_DATA_DIRECTORY_NAME);
- ensurePropertyIsAbsolutePath(props, PATH_WEB, DEFAULT_WEB_DIRECTORY_NAME);
- ensurePropertyIsAbsolutePath(props, PATH_LOGS, DEFAULT_LOGS_DIRECTORY_NAME);
- ensurePropertyIsAbsolutePath(props, PATH_TEMP, DEFAULT_TEMP_DIRECTORY_NAME);
- this.initialized = true;
+ private final AppSettings settings;
+
+ public AppFileSystem(AppSettings settings) {
+ this.settings = settings;
}
- /**
- * Must be called after {@link #verifyProps()}
- */
@Override
public void reset() throws IOException {
- if (!initialized) {
- throw new IllegalStateException("method verifyProps must be called first");
- }
- createDirectory(props, PATH_DATA);
- createDirectory(props, PATH_WEB);
- createDirectory(props, PATH_LOGS);
- File tempDir = createOrCleanTempDirectory(props, PATH_TEMP);
+ createDirectory(PATH_DATA);
+ createDirectory(PATH_WEB);
+ createDirectory(PATH_LOGS);
+ File tempDir = createOrCleanTempDirectory(PATH_TEMP);
try (AllProcessesCommands allProcessesCommands = new AllProcessesCommands(tempDir)) {
allProcessesCommands.clean();
}
@@ -91,31 +67,19 @@ public class AppFileSystem implements FileSystem {
@Override
public File getTempDir() {
- return props.nonNullValueAsFile(PATH_TEMP);
- }
-
- private File ensurePropertyIsAbsolutePath(Props props, String propKey, String defaultRelativePath) {
- String path = props.value(propKey, defaultRelativePath);
- 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;
+ return settings.getProps().nonNullValueAsFile(PATH_TEMP);
}
- private static boolean createDirectory(Props props, String propKey) throws IOException {
- File dir = props.nonNullValueAsFile(propKey);
+ private boolean createDirectory(String propKey) throws IOException {
+ File dir = settings.getProps().nonNullValueAsFile(propKey);
if (dir.exists()) {
ensureIsNotAFile(propKey, dir);
return false;
- } else {
- LOG.trace("forceMkdir {}", dir.getAbsolutePath());
- forceMkdir(dir);
- ensureIsNotAFile(propKey, dir);
- return true;
}
+
+ forceMkdir(dir);
+ ensureIsNotAFile(propKey, dir);
+ return true;
}
private static void ensureIsNotAFile(String propKey, File dir) {
@@ -125,38 +89,23 @@ public class AppFileSystem implements FileSystem {
}
}
- private static File createOrCleanTempDirectory(Props props, String propKey) throws IOException {
- File dir = props.nonNullValueAsFile(propKey);
+ private File createOrCleanTempDirectory(String propKey) throws IOException {
+ File dir = settings.getProps().nonNullValueAsFile(propKey);
LOG.info("Cleaning or creating temp directory {}", dir.getAbsolutePath());
- if (!createDirectory(props, propKey)) {
+ if (!createDirectory(propKey)) {
Files.walkFileTree(dir.toPath(), FOLLOW_LINKS, CleanTempDirFileVisitor.VISIT_MAX_DEPTH, new CleanTempDirFileVisitor(dir.toPath()));
}
return dir;
}
- public void ensureUnchangedConfiguration(Props newProps) {
- verifyUnchanged(newProps, PATH_DATA, DEFAULT_DATA_DIRECTORY_NAME);
- verifyUnchanged(newProps, PATH_WEB, DEFAULT_WEB_DIRECTORY_NAME);
- verifyUnchanged(newProps, PATH_LOGS, DEFAULT_LOGS_DIRECTORY_NAME);
- verifyUnchanged(newProps, PATH_TEMP, DEFAULT_TEMP_DIRECTORY_NAME);
- }
-
- private void verifyUnchanged(Props newProps, String propKey, String defaultRelativePath) {
- String initialValue = props.value(propKey, defaultRelativePath);
- String newValue = newProps.value(propKey, defaultRelativePath);
- if (!Objects.equals(newValue, initialValue)) {
- throw new IllegalStateException(format("Change of property '%s' is not supported ('%s'=> '%s')", propKey, initialValue, newValue));
- }
- }
-
private static class CleanTempDirFileVisitor extends SimpleFileVisitor<Path> {
private static final Path SHAREDMEMORY_FILE = Paths.get("sharedmemory");
- public static final int VISIT_MAX_DEPTH = 1;
+ static final int VISIT_MAX_DEPTH = 1;
private final Path path;
private final boolean symLink;
- public CleanTempDirFileVisitor(Path path) {
+ CleanTempDirFileVisitor(Path path) {
this.path = path;
this.symLink = Files.isSymbolicLink(path);
}
diff --git a/sonar-application/src/main/java/org/sonar/application/AppLogging.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java
index 7ea96ff62bb..2755f893c55 100644
--- a/sonar-application/src/main/java/org/sonar/application/AppLogging.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java
@@ -25,15 +25,17 @@ 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.Props;
+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;
-import static org.sonar.process.monitor.StreamGobbler.LOGGER_GOBBLER;
/**
* Configure logback for the APP process.
@@ -105,7 +107,7 @@ import static org.sonar.process.monitor.StreamGobbler.LOGGER_GOBBLER;
* </p>
*
*/
-class AppLogging {
+public class AppLogging {
private static final String CONSOLE_LOGGER = "console";
private static final String CONSOLE_PLAIN_APPENDER = "CONSOLE";
@@ -116,24 +118,32 @@ class AppLogging {
.build();
private final LogbackHelper helper = new LogbackHelper();
+ private final AppSettings appSettings;
- LoggerContext configure(Props props) {
+ public AppLogging(AppSettings appSettings) {
+ this.appSettings = appSettings;
+ }
+
+ public LoggerContext configure() {
LoggerContext ctx = helper.getRootContext();
ctx.reset();
helper.enableJulChangePropagation(ctx);
configureConsole(ctx);
- if (helper.isAllLogsToConsoleEnabled(props) || !props.valueAsBoolean("sonar.wrapped", false)) {
- configureWithLogbackWritingToFile(props, ctx);
+ if (helper.isAllLogsToConsoleEnabled(appSettings.getProps()) || !appSettings.getProps().valueAsBoolean("sonar.wrapped", false)) {
+ configureWithLogbackWritingToFile(ctx);
} else {
configureWithWrapperWritingToFile(ctx);
}
helper.apply(
LogLevelConfig.newBuilder()
.rootLevelFor(ProcessId.APP)
- .immutableLevel("com.hazelcast", Level.toLevel(props.value(ClusterParameters.HAZELCAST_LOG_LEVEL.getName())))
- .build(), props);
+ .immutableLevel("com.hazelcast",
+ Level.toLevel(
+ appSettings.getProps().nonNullValue(ProcessProperties.HAZELCAST_LOG_LEVEL)))
+ .build(),
+ appSettings.getProps());
return ctx;
}
@@ -156,12 +166,12 @@ class AppLogging {
* 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(Props props, LoggerContext ctx) {
+ 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(props, ctx);
+ 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.
@@ -195,20 +205,20 @@ class AppLogging {
configureGobbler(ctx);
}
- private void configureRootWithLogbackWritingToFile(Props props, LoggerContext 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, props, APP_ROOT_LOGGER_CONFIG, appLogPattern);
+ 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 org.sonar.process.monitor.StreamGobbler#LOGGER_GOBBLER}) by {@link org.sonar.process.monitor.StreamGobbler},
+ * (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 org.sonar.process.monitor.StreamGobbler#LOGGER_GOBBLER} and only this one)</li>
+ * <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>
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/app/StartupBarrierFactory.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloader.java
index 278f89a408d..f475e10f20c 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/app/StartupBarrierFactory.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloader.java
@@ -17,17 +17,20 @@
* 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.ce.app;
+package org.sonar.application;
-import org.sonar.process.ProcessEntryPoint;
-import org.sonar.process.ProcessProperties;
+import java.io.IOException;
+import org.sonar.application.config.AppSettings;
-class StartupBarrierFactory {
+/**
+ * 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;
- public StartupBarrier create(ProcessEntryPoint entryPoint) {
- if (entryPoint.getProps().valueAsBoolean(ProcessProperties.CLUSTER_WEB_DISABLED)) {
- return () -> true;
- }
- return new WebServerBarrier(entryPoint.getSharedDir());
- }
}
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
new file mode 100644
index 00000000000..3463c1effd6
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppReloaderImpl.java
@@ -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-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java
index 53112e5935b..0f536a8aa10 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java
@@ -17,33 +17,38 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application;
-import java.io.File;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
import org.sonar.process.ProcessId;
-public class JavaProcessLauncherTest {
+public interface AppState extends AutoCloseable {
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
+ void addListener(AppStateListener listener);
- @Test
- public void fail_to_launch() throws Exception {
- File tempDir = temp.newFolder();
- JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
- JavaProcessLauncher launcher = new JavaProcessLauncher(new Timeouts(), tempDir);
+ /**
+ * 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);
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Fail to launch [es]");
+ /**
+ * Mark local process as operational. In cluster mode, this
+ * event is propagated to all nodes.
+ */
+ void setOperational(ProcessId processId);
- // command is not correct (missing options), java.lang.ProcessBuilder#start()
- // throws an exception
- launcher.launch(command);
- }
+ boolean tryToLockWebLeader();
+
+ void reset();
+
+ @Override
+ void close();
}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateFactory.java
index e1161fbaf26..196aa2a9fa0 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateFactory.java
@@ -17,23 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application;
-import org.junit.Test;
+import org.sonar.application.cluster.AppStateClusterImpl;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.ClusterSettings;
-import static org.assertj.core.api.Assertions.assertThat;
+public class AppStateFactory {
-public class TimeoutsTest {
+ private final AppSettings settings;
- @Test
- public void test_default_values() throws Exception {
- Timeouts timeouts = new Timeouts();
- assertThat(timeouts.getTerminationTimeout()).isGreaterThan(1000L);
+ public AppStateFactory(AppSettings settings) {
+ this.settings = settings;
}
- @Test
- public void test_values() throws Exception {
- Timeouts timeouts = new Timeouts(3L);
- assertThat(timeouts.getTerminationTimeout()).isEqualTo(3L);
+ 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
new file mode 100644
index 00000000000..71177205b41
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nonnull;
+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 close() {
+ // nothing to do
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateListener.java
index c886b0cc5f0..581ea4004aa 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateListener.java
@@ -17,19 +17,18 @@
* 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.server.platform.cluster;
+package org.sonar.application;
-import org.junit.Test;
-import org.sonar.test.TestUtils;
+import org.sonar.process.ProcessId;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ClusterPropertiesTest {
-
- @Test
- public void test_Definitions() {
- assertThat(ClusterProperties.definitions()).isNotEmpty();
- assertThat(TestUtils.hasOnlyPrivateConstructors(ClusterProperties.class)).isTrue();
- }
+@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/process/monitor/FileSystem.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/FileSystem.java
index fd9e335cf35..efdbcd2853f 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/FileSystem.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/FileSystem.java
@@ -17,13 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+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
new file mode 100644
index 00000000000..62e09a6adab
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/NodeLifecycle.java
@@ -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-ce/src/main/java/org/sonar/ce/app/StartupBarrier.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/Scheduler.java
index 4113242273c..f69b61d2190 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/app/StartupBarrier.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/Scheduler.java
@@ -17,14 +17,19 @@
* 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.ce.app;
+package org.sonar.application;
+
+public interface Scheduler {
+
+ void schedule();
+
+ /**
+ * Stops all processes and waits for them to be down.
+ */
+ void terminate();
-interface StartupBarrier {
/**
- * This blocking call, waits for a process (or anything) to be operational until either it is actually operational, or
- * the calling thread is interrupted.
- *
- * @return true if what's awaited for is operational, false otherwise
+ * Blocks until all processes are down
*/
- boolean waitForOperational();
+ 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
new file mode 100644
index 00000000000..200127f8da1
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/SchedulerImpl.java
@@ -0,0 +1,292 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.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.application.process.JavaCommand;
+import org.sonar.application.process.JavaCommandFactory;
+import org.sonar.application.process.JavaProcessLauncher;
+import org.sonar.application.process.Lifecycle;
+import org.sonar.application.process.ProcessEventListener;
+import org.sonar.application.process.ProcessLifecycleListener;
+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 JavaCommandFactory javaCommandFactory;
+ private final JavaProcessLauncher javaProcessLauncher;
+ 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();
+ private StopperThread stopperThread;
+ private RestarterThread restarterThread;
+ private long processWatcherDelayMs = SQProcess.DEFAULT_WATCHER_DELAY_MS;
+
+ public SchedulerImpl(AppSettings settings, AppReloader appReloader, JavaCommandFactory javaCommandFactory,
+ JavaProcessLauncher javaProcessLauncher,
+ AppState appState) {
+ this.settings = settings;
+ this.appReloader = appReloader;
+ this.javaCommandFactory = javaCommandFactory;
+ this.javaProcessLauncher = javaProcessLauncher;
+ 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());
+ stopCountDown.set(processesById.size());
+
+ tryToStartAll();
+ }
+
+ private void tryToStartAll() {
+ try {
+ tryToStartEs();
+ tryToStartWeb();
+ tryToStartCe();
+ } catch (RuntimeException e) {
+ terminate();
+ }
+ }
+
+ private void tryToStartEs() {
+ SQProcess process = processesById.get(ProcessId.ELASTICSEARCH);
+ if (process != null) {
+ tryToStartProcess(process, javaCommandFactory::createEsCommand);
+ }
+ }
+
+ private void tryToStartWeb() {
+ SQProcess process = processesById.get(ProcessId.WEB_SERVER);
+ if (process == null || !isEsClientStartable()) {
+ return;
+ }
+ if (appState.isOperational(ProcessId.WEB_SERVER, false)) {
+ tryToStartProcess(process, () -> javaCommandFactory.createWebCommand(false));
+ } else if (appState.tryToLockWebLeader()) {
+ tryToStartProcess(process, () -> javaCommandFactory.createWebCommand(true));
+ }
+ }
+
+ private void tryToStartCe() {
+ SQProcess process = processesById.get(ProcessId.COMPUTE_ENGINE);
+ if (process != null && appState.isOperational(ProcessId.WEB_SERVER, false) && isEsClientStartable()) {
+ tryToStartProcess(process, javaCommandFactory::createCeCommand);
+ }
+ }
+
+ private boolean isEsClientStartable() {
+ boolean requireLocalEs = ClusterSettings.isLocalElasticsearchEnabled(settings);
+ return appState.isOperational(ProcessId.ELASTICSEARCH, requireLocalEs);
+ }
+
+ private void tryToStartProcess(SQProcess process, Supplier<JavaCommand> commandSupplier) {
+ try {
+ process.start(() -> {
+ JavaCommand command = commandSupplier.get();
+ return javaProcessLauncher.launch(command);
+ });
+ } 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) {
+ if (to == Lifecycle.State.STOPPED) {
+ onProcessStop(processId);
+ }
+ }
+
+ 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
new file mode 100644
index 00000000000..afd0ebd4381
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java
@@ -0,0 +1,214 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.EntryEvent;
+import com.hazelcast.core.EntryListener;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.core.IAtomicReference;
+import com.hazelcast.core.ILock;
+import com.hazelcast.core.MapEvent;
+import com.hazelcast.core.ReplicatedMap;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.AppState;
+import org.sonar.application.AppStateListener;
+import org.sonar.process.ProcessId;
+
+public class AppStateClusterImpl implements AppState {
+ static final String OPERATIONAL_PROCESSES = "operational_processes";
+ private static final String LEADER = "leader";
+
+ private final List<AppStateListener> listeners = new ArrayList<>();
+ private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
+ private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class);
+ private final String listenerUuid;
+
+ final HazelcastInstance hzInstance;
+
+ public AppStateClusterImpl(AppSettings appSettings) {
+ ClusterProperties clusterProperties = new ClusterProperties(appSettings);
+ clusterProperties.validate();
+
+ if (!clusterProperties.isEnabled()) {
+ throw new IllegalStateException("Cluster is not enabled on this instance");
+ }
+
+ Config hzConfig = new Config();
+ try {
+ hzConfig.setInstanceName(InetAddress.getLocalHost().getHostName());
+ } catch (UnknownHostException e) {
+ // Ignore it
+ }
+
+ hzConfig.getGroupConfig().setName(clusterProperties.getName());
+
+ // Configure the network instance
+ NetworkConfig netConfig = hzConfig.getNetworkConfig();
+ netConfig.setPort(clusterProperties.getPort());
+
+ if (!clusterProperties.getInterfaces().isEmpty()) {
+ netConfig.getInterfaces()
+ .setEnabled(true)
+ .setInterfaces(clusterProperties.getInterfaces());
+ }
+
+ // 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.getMembers());
+
+ // 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");
+
+ // We are not using the partition group of Hazelcast, so disabling it
+ hzConfig.getPartitionGroupConfig().setEnabled(false);
+
+ hzInstance = Hazelcast.newHazelcastInstance(hzConfig);
+ operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
+ listenerUuid = operationalProcesses.addEntryListener(new OperationalProcessListener());
+ }
+
+ @Override
+ public void addListener(@Nonnull AppStateListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public boolean isOperational(@Nonnull ProcessId processId, boolean local) {
+ if (local) {
+ return localProcesses.computeIfAbsent(processId, p -> false);
+ }
+ for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
+ if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setOperational(@Nonnull ProcessId processId) {
+ localProcesses.put(processId, true);
+ operationalProcesses.put(new ClusterProcess(getLocalUuid(), processId), Boolean.TRUE);
+ }
+
+ @Override
+ public 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;
+ }
+ }
+
+ @Override
+ public void reset() {
+ throw new IllegalStateException("state reset is not supported in cluster mode");
+ }
+
+ @Override
+ public void close() {
+ if (hzInstance != null) {
+ operationalProcesses.removeEntryListener(listenerUuid);
+ operationalProcesses.keySet().forEach(
+ clusterNodeProcess -> {
+ if (clusterNodeProcess.getNodeUuid().equals(getLocalUuid())) {
+ operationalProcesses.remove(clusterNodeProcess);
+ }
+ });
+ hzInstance.shutdown();
+ }
+ }
+
+ private String getLocalUuid() {
+ return hzInstance.getLocalEndpoint().getUuid();
+ }
+
+ 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
+ }
+ }
+}
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
new file mode 100644
index 00000000000..dbd823b971f
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProcess.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 javax.annotation.Nonnull;
+import org.sonar.process.ProcessId;
+
+public class ClusterProcess implements Serializable {
+ private final ProcessId processId;
+ private final String nodeUuid;
+
+ public ClusterProcess(@Nonnull String nodeUuid, @Nonnull ProcessId processId) {
+ this.processId = processId;
+ this.nodeUuid = nodeUuid;
+ }
+
+ public ProcessId getProcessId() {
+ return processId;
+ }
+
+ public String getNodeUuid() {
+ return nodeUuid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ClusterProcess)) {
+ return false;
+ }
+
+ ClusterProcess that = (ClusterProcess) o;
+
+ if (processId != that.processId) {
+ return false;
+ }
+ return nodeUuid != null ? nodeUuid.equals(that.nodeUuid) : that.nodeUuid == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = processId != null ? processId.hashCode() : 0;
+ result = 31 * result + (nodeUuid != null ? nodeUuid.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProperties.java
index 1b86838ac5c..6c9ea1b8224 100644
--- a/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/ClusterProperties.java
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.application;
+package org.sonar.application.cluster;
import java.net.InetAddress;
import java.net.NetworkInterface;
@@ -26,52 +26,38 @@ import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
-import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.sonar.process.Props;
+import org.sonar.application.config.AppSettings;
+import org.sonar.process.ProcessProperties;
/**
* Properties of the cluster configuration
*/
-final class ClusterProperties {
+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 boolean portAutoincrement;
private final List<String> members;
private final List<String> interfaces;
private final String name;
- private final String logLevel;
- ClusterProperties(@Nonnull Props props) {
- port = props.valueAsInt(ClusterParameters.PORT.getName(), ClusterParameters.PORT.getDefaultValueAsInt());
- enabled = props.valueAsBoolean(ClusterParameters.ENABLED.getName(), ClusterParameters.ENABLED.getDefaultValueAsBoolean());
- portAutoincrement = props.valueAsBoolean(ClusterParameters.PORT_AUTOINCREMENT.getName(), ClusterParameters.PORT_AUTOINCREMENT.getDefaultValueAsBoolean());
+ ClusterProperties(AppSettings appSettings) {
+ port = appSettings.getProps().valueAsInt(ProcessProperties.CLUSTER_PORT);
+ enabled = appSettings.getProps().valueAsBoolean(ProcessProperties.CLUSTER_ENABLED);
interfaces = extractInterfaces(
- props.value(ClusterParameters.INTERFACES.getName(), ClusterParameters.INTERFACES.getDefaultValue())
+ appSettings.getProps().value(ProcessProperties.CLUSTER_INTERFACES, "")
);
- name = props.value(ClusterParameters.NAME.getName(), ClusterParameters.NAME.getDefaultValue());
- logLevel = props.value(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), ClusterParameters.HAZELCAST_LOG_LEVEL.getDefaultValue());
+ name = appSettings.getProps().value(ProcessProperties.CLUSTER_NAME);
members = extractMembers(
- props.value(ClusterParameters.MEMBERS.getName(), ClusterParameters.MEMBERS.getDefaultValue())
+ appSettings.getProps().value(ProcessProperties.CLUSTER_MEMBERS, "")
);
}
- void populateProps(@Nonnull Props props) {
- props.set(ClusterParameters.PORT.getName(), Integer.toString(port));
- props.set(ClusterParameters.ENABLED.getName(), Boolean.toString(enabled));
- props.set(ClusterParameters.PORT_AUTOINCREMENT.getName(), Boolean.toString(portAutoincrement));
- props.set(ClusterParameters.INTERFACES.getName(), interfaces.stream().collect(Collectors.joining(",")));
- props.set(ClusterParameters.NAME.getName(), name);
- props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), logLevel);
- props.set(ClusterParameters.MEMBERS.getName(), members.stream().collect(Collectors.joining(",")));
- }
-
int getPort() {
return port;
}
@@ -80,10 +66,6 @@ final class ClusterProperties {
return enabled;
}
- boolean isPortAutoincrement() {
- return portAutoincrement;
- }
-
List<String> getMembers() {
return members;
}
@@ -96,10 +78,6 @@ final class ClusterProperties {
return name;
}
- String getLogLevel() {
- return logLevel;
- }
-
void validate() {
if (!enabled) {
return;
@@ -108,7 +86,7 @@ final class ClusterProperties {
checkArgument(
StringUtils.isNotEmpty(name),
"Cluster have been enabled but a %s has not been defined.",
- ClusterParameters.NAME.getName()
+ ProcessProperties.CLUSTER_NAME
);
// Test validity of port
@@ -140,7 +118,7 @@ final class ClusterProperties {
if (StringUtils.isNotEmpty(member)) {
if (!member.contains(":")) {
result.add(
- String.format("%s:%s", member, ClusterParameters.PORT.getDefaultValue())
+ String.format("%s:%s", member, DEFAULT_PORT)
);
} else {
result.add(member);
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
new file mode 100644
index 00000000000..4501fba7bf3
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/package-info.java
@@ -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-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
new file mode 100644
index 00000000000..784e7b1f173
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettings.java
@@ -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-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsImpl.java
index 51d3d59bcca..02828a78d11 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsImpl.java
@@ -17,28 +17,31 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.config;
-/**
- * Most of the timeouts involved in process monitoring, in milliseconds
- */
-class Timeouts {
+import java.util.Optional;
+import org.sonar.process.Props;
+
+public class AppSettingsImpl implements AppSettings {
- private final long terminationTimeout;
+ private Props props;
- Timeouts(long terminationTimeout) {
- this.terminationTimeout = terminationTimeout;
+ AppSettingsImpl(Props props) {
+ this.props = props;
}
- public Timeouts() {
- this(60_000L);
+ @Override
+ public Props getProps() {
+ return props;
}
- /**
- * [both monitor and monitored process] timeout of graceful termination before hard killing
- */
- long getTerminationTimeout() {
- return terminationTimeout;
+ @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
new file mode 100644
index 00000000000..5362c050370
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoader.java
@@ -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/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java
index 32f371dce83..50436b6468c 100644
--- a/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/AppSettingsLoaderImpl.java
@@ -17,7 +17,7 @@
* 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;
+package org.sonar.application.config;
import java.io.File;
import java.io.FileInputStream;
@@ -25,34 +25,40 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
+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;
-class PropsBuilder {
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class AppSettingsLoaderImpl implements AppSettingsLoader {
private final File homeDir;
- private final JdbcSettings jdbcSettings;
- private final Properties rawProperties;
+ private final String[] cliArguments;
+ private final Consumer<Props>[] consumers;
+
+ public AppSettingsLoaderImpl(String[] cliArguments) {
+ this(cliArguments, detectHomeDir(), new FileSystemSettings(), new JdbcSettings(), new ClusterSettings());
+ }
- PropsBuilder(Properties rawProperties, JdbcSettings jdbcSettings, File homeDir) {
- this.rawProperties = rawProperties;
- this.jdbcSettings = jdbcSettings;
+ AppSettingsLoaderImpl(String[] cliArguments, File homeDir, Consumer<Props>... consumers) {
+ this.cliArguments = cliArguments;
this.homeDir = homeDir;
+ this.consumers = consumers;
}
- PropsBuilder(Properties rawProperties, JdbcSettings jdbcSettings) {
- this(rawProperties, jdbcSettings, detectHomeDir());
+ File getHomeDir() {
+ return homeDir;
}
- /**
- * Load optional conf/sonar.properties, interpolates environment variables
- */
- Props build() {
+ @Override
+ public AppSettings load() {
Properties p = loadPropertiesFile(homeDir);
- p.putAll(rawProperties);
+ p.putAll(CommandLineParser.parseArguments(cliArguments));
p.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
p = ConfigurationUtils.interpolateVariables(p, System.getenv());
@@ -61,31 +67,35 @@ class PropsBuilder {
// are accessed
Props props = new Props(p);
ProcessProperties.completeDefaults(props);
+ Arrays.stream(consumers).forEach(c -> c.accept(props));
- // check JDBC properties and set path to driver
- jdbcSettings.checkAndComplete(homeDir, props);
-
- return props;
+ return new AppSettingsImpl(props);
}
- static File detectHomeDir() {
+ private static File detectHomeDir() {
try {
- File appJar = new File(PropsBuilder.class.getProtectionDomain().getCodeSource().getLocation().toURI());
+ 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), StandardCharsets.UTF_8)) {
+ 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
new file mode 100644
index 00000000000..dd2318d6964
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/ClusterSettings.java
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+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.CLUSTER_WEB_LEADER;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+
+public class ClusterSettings implements Consumer<Props> {
+
+ @Override
+ public void accept(Props props) {
+ if (!isClusterEnabled(props)) {
+ return;
+ }
+ if (props.value(CLUSTER_WEB_LEADER) != null) {
+ throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_LEADER));
+ }
+ String jdbcUrl = props.value(JDBC_URL);
+ if (jdbcUrl == null || jdbcUrl.startsWith("jdbc:h2:")) {
+ throw new MessageException("Embedded database is not supported in cluster mode");
+ }
+ }
+
+ 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/sonar-application/src/main/java/org/sonar/application/CommandLineParser.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/CommandLineParser.java
index 35642d22c71..14422fbfaf2 100644
--- a/sonar-application/src/main/java/org/sonar/application/CommandLineParser.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/CommandLineParser.java
@@ -17,18 +17,23 @@
* 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;
+package org.sonar.application.config;
import org.apache.commons.lang.StringUtils;
import java.util.Map;
import java.util.Properties;
-class CommandLineParser {
+public class CommandLineParser {
+
+ private CommandLineParser() {
+ // prevent instantiation
+ }
+
/**
* Build properties from command-line arguments and system properties
*/
- Properties parseArguments(String[] args) {
+ public static Properties parseArguments(String[] args) {
Properties props = argumentsToProperties(args);
// complete with only the system properties that start with "sonar."
@@ -44,7 +49,7 @@ class CommandLineParser {
/**
* Convert strings "-Dkey=value" to properties
*/
- Properties argumentsToProperties(String[] args) {
+ static Properties argumentsToProperties(String[] args) {
Properties props = new Properties();
for (String arg : args) {
if (!arg.startsWith("-D") || !arg.contains("=")) {
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
new file mode 100644
index 00000000000..b7953aca498
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/FileSystemSettings.java
@@ -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/sonar-application/src/main/java/org/sonar/application/JdbcSettings.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/JdbcSettings.java
index 82b137a6b02..52f772958fc 100644
--- a/sonar-application/src/main/java/org/sonar/application/JdbcSettings.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/JdbcSettings.java
@@ -17,11 +17,12 @@
* 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;
+package org.sonar.application.config;
import java.io.File;
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;
@@ -34,12 +35,12 @@ import org.sonar.process.Props;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT;
-import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT_DEFAULT_VALUE;
-import static org.sonar.api.database.DatabaseProperties.PROP_URL;
+import static org.sonar.process.ProcessProperties.JDBC_EMBEDDED_PORT;
import static org.sonar.process.ProcessProperties.JDBC_URL;
-public class JdbcSettings {
+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"),
@@ -52,7 +53,9 @@ public class JdbcSettings {
}
}
- public void checkAndComplete(File homeDir, Props props) {
+ @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);
@@ -78,18 +81,18 @@ public class JdbcSettings {
Provider resolveProviderAndEnforceNonnullJdbcUrl(Props props) {
String url = props.value(JDBC_URL);
- String embeddedDatabasePort = props.value(PROP_EMBEDDED_PORT);
+ Integer embeddedDatabasePort = props.valueAsInt(JDBC_EMBEDDED_PORT);
- if (isNotEmpty(embeddedDatabasePort)) {
+ if (embeddedDatabasePort != null) {
String correctUrl = buildH2JdbcUrl(embeddedDatabasePort);
warnIfUrlIsSet(embeddedDatabasePort, url, correctUrl);
- props.set(PROP_URL, correctUrl);
+ props.set(JDBC_URL, correctUrl);
return Provider.H2;
}
if (isEmpty(url)) {
- props.set(PROP_URL, buildH2JdbcUrl(PROP_EMBEDDED_PORT_DEFAULT_VALUE));
- props.set(PROP_EMBEDDED_PORT, PROP_EMBEDDED_PORT_DEFAULT_VALUE);
+ 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;
}
@@ -106,7 +109,7 @@ public class JdbcSettings {
}
}
- private static String buildH2JdbcUrl(String embeddedDatabasePort) {
+ private static String buildH2JdbcUrl(int embeddedDatabasePort) {
return "jdbc:h2:tcp://localhost:" + embeddedDatabasePort + "/sonar";
}
@@ -119,23 +122,23 @@ public class JdbcSettings {
}
}
- private static void warnIfUrlIsSet(String port, String existing, String expectedUrl) {
+ 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.",
- PROP_EMBEDDED_PORT, port,
- PROP_URL);
+ 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 '{}'.",
- PROP_EMBEDDED_PORT, PROP_URL,
- PROP_URL, existing, PROP_EMBEDDED_PORT, port,
- PROP_URL, expectedUrl,
- PROP_URL, PROP_EMBEDDED_PORT);
+ JDBC_EMBEDDED_PORT, JDBC_URL,
+ JDBC_URL, existing, JDBC_EMBEDDED_PORT, port,
+ JDBC_URL, expectedUrl,
+ JDBC_URL, JDBC_EMBEDDED_PORT);
}
}
}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/package-info.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/package-info.java
index e1e93fd1bb4..7dc3ab211ef 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/package-info.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/package-info.java
@@ -18,6 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
@ParametersAreNonnullByDefault
-package org.sonar.process.monitor;
+package org.sonar.application;
import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommand.java
index db0d4fa231e..b5311087de7 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommand.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.process;
import java.io.File;
import java.util.ArrayList;
@@ -27,7 +27,6 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
import org.sonar.process.ProcessId;
public class JavaCommand {
@@ -73,7 +72,7 @@ public class JavaCommand {
}
public JavaCommand addJavaOption(String s) {
- if (StringUtils.isNotBlank(s)) {
+ if (!s.isEmpty()) {
javaOptions.add(s);
}
return this;
@@ -139,7 +138,7 @@ public class JavaCommand {
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("JavaCommand{");
+ StringBuilder sb = new StringBuilder("JavaCommand{");
sb.append("workDir=").append(workDir);
sb.append(", javaOptions=").append(javaOptions);
sb.append(", className='").append(className).append('\'');
diff --git a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactory.java
index 0071a3619d8..9d56f2c4e5c 100644
--- a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactory.java
@@ -17,16 +17,14 @@
* 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 org.sonar.process.Props;
-import org.sonar.process.monitor.JavaCommand;
+package org.sonar.application.process;
public interface JavaCommandFactory {
- JavaCommand createESCommand(Props props, File homeDir);
- JavaCommand createWebCommand(Props props, File homeDir);
+ JavaCommand createEsCommand();
+
+ JavaCommand createWebCommand(boolean leader);
+
+ JavaCommand createCeCommand();
- JavaCommand createCeCommand(Props props, File homeDir);
}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactoryImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactoryImpl.java
new file mode 100644
index 00000000000..86f4fd8c476
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaCommandFactoryImpl.java
@@ -0,0 +1,124 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.config.AppSettings;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+
+import java.io.File;
+import java.util.Optional;
+
+import static org.sonar.process.ProcessProperties.*;
+
+public class JavaCommandFactoryImpl implements JavaCommandFactory {
+ /**
+ * Properties about proxy that must be set as system properties
+ */
+ private static final String[] PROXY_PROPERTY_KEYS = new String[] {
+ HTTP_PROXY_HOST,
+ HTTP_PROXY_PORT,
+ "http.nonProxyHosts",
+ HTTPS_PROXY_HOST,
+ HTTPS_PROXY_PORT,
+ "http.auth.ntlm.domain",
+ "socksProxyHost",
+ "socksProxyPort"};
+
+ private final AppSettings settings;
+
+ public JavaCommandFactoryImpl(AppSettings settings) {
+ this.settings = settings;
+ }
+
+ @Override
+ public JavaCommand createEsCommand() {
+ File homeDir = settings.getProps().nonNullValueAsFile(ProcessProperties.PATH_HOME);
+ return newJavaCommand(ProcessId.ELASTICSEARCH, homeDir)
+ .addJavaOptions("-Djava.awt.headless=true")
+ .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
+ .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
+ .setClassName("org.sonar.search.SearchServer")
+ .addClasspath("./lib/common/*")
+ .addClasspath("./lib/search/*");
+ }
+
+ @Override
+ public JavaCommand createWebCommand(boolean leader) {
+ File homeDir = settings.getProps().nonNullValueAsFile(ProcessProperties.PATH_HOME);
+ JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, homeDir)
+ .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS)
+ .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.WEB_JAVA_OPTS))
+ .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS))
+ // required for logback tomcat valve
+ .setEnvVariable(ProcessProperties.PATH_LOGS, settings.getProps().nonNullValue(ProcessProperties.PATH_LOGS))
+ .setArgument("sonar.cluster.web.startupLeader", Boolean.toString(leader))
+ .setClassName("org.sonar.server.app.WebServer")
+ .addClasspath("./lib/common/*")
+ .addClasspath("./lib/server/*");
+ String driverPath = settings.getProps().value(ProcessProperties.JDBC_DRIVER_PATH);
+ if (driverPath != null) {
+ command.addClasspath(driverPath);
+ }
+ return command;
+ }
+
+ @Override
+ public JavaCommand createCeCommand() {
+ File homeDir = settings.getProps().nonNullValueAsFile(ProcessProperties.PATH_HOME);
+ JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, homeDir)
+ .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS)
+ .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.CE_JAVA_OPTS))
+ .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS))
+ .setClassName("org.sonar.ce.app.CeServer")
+ .addClasspath("./lib/common/*")
+ .addClasspath("./lib/server/*")
+ .addClasspath("./lib/ce/*");
+ String driverPath = settings.getProps().value(ProcessProperties.JDBC_DRIVER_PATH);
+ if (driverPath != null) {
+ command.addClasspath(driverPath);
+ }
+ return command;
+ }
+
+ private JavaCommand newJavaCommand(ProcessId id, File homeDir) {
+ JavaCommand command = new JavaCommand(id)
+ .setWorkDir(homeDir)
+ .setArguments(settings.getProps().rawProperties());
+
+ for (String key : PROXY_PROPERTY_KEYS) {
+ settings.getValue(key).ifPresent(val -> command.addJavaOption("-D" + key + "=" + val));
+ }
+
+ // defaults of HTTPS are the same than HTTP defaults
+ setSystemPropertyToDefaultIfNotSet(command, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
+ setSystemPropertyToDefaultIfNotSet(command, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
+ return command;
+ }
+
+ private void setSystemPropertyToDefaultIfNotSet(JavaCommand command,
+ String httpsProperty, String httpProperty) {
+ Optional<String> httpValue = settings.getValue(httpProperty);
+ Optional<String> httpsValue = settings.getValue(httpsProperty);
+ if (!httpsValue.isPresent() && httpValue.isPresent()) {
+ command.addJavaOption("-D" + httpsProperty + "=" + httpValue.get());
+ }
+ }
+}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncher.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncher.java
new file mode 100644
index 00000000000..93e7c677424
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncher.java
@@ -0,0 +1,87 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public interface JavaProcessLauncher extends Closeable {
+
+ class SystemProcessBuilder {
+ private final ProcessBuilder builder = new ProcessBuilder();
+
+ /**
+ * @see ProcessBuilder#command()
+ */
+ public List<String> command() {
+ return builder.command();
+ }
+
+ /**
+ * @see ProcessBuilder#command(List)
+ */
+ public SystemProcessBuilder command(List<String> commands) {
+ builder.command(commands);
+ return this;
+ }
+
+ /**
+ * @see ProcessBuilder#directory(File)
+ */
+ public SystemProcessBuilder directory(File dir) {
+ builder.directory(dir);
+ return this;
+ }
+
+ /**
+ * @see ProcessBuilder#environment()
+ */
+ public Map<String, String> environment() {
+ return builder.environment();
+ }
+
+ /**
+ * @see ProcessBuilder#redirectErrorStream(boolean)
+ */
+ public SystemProcessBuilder redirectErrorStream(boolean b) {
+ builder.redirectErrorStream(b);
+ return this;
+ }
+
+ /**
+ * @see ProcessBuilder#start()
+ */
+ public Process start() throws IOException {
+ return builder.start();
+ }
+ }
+
+ @Override
+ void close();
+
+ /**
+ * Launch Java command. An {@link IllegalStateException} is thrown
+ * on error.
+ */
+ ProcessMonitor launch(JavaCommand javaCommand);
+}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncherImpl.java
index aa7534cf0fb..c02aedd0046 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/JavaProcessLauncherImpl.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.process;
import java.io.File;
import java.io.FileOutputStream;
@@ -26,67 +26,73 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
-import org.apache.commons.lang.StringUtils;
+import java.util.function.Supplier;
+import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.process.AllProcessesCommands;
import org.sonar.process.ProcessCommands;
-import org.sonar.process.ProcessEntryPoint;
-import org.sonar.process.ProcessUtils;
+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;
-class JavaProcessLauncher {
+public class JavaProcessLauncherImpl implements JavaProcessLauncher {
+ private static final Logger LOG = LoggerFactory.getLogger(JavaProcessLauncherImpl.class);
- private final Timeouts timeouts;
private final File tempDir;
private final AllProcessesCommands allProcessesCommands;
+ private final Supplier<SystemProcessBuilder> processBuilderSupplier;
- JavaProcessLauncher(Timeouts timeouts, File tempDir) {
- this.timeouts = timeouts;
+ public JavaProcessLauncherImpl(File tempDir) {
+ this(tempDir, new AllProcessesCommands(tempDir), SystemProcessBuilder::new);
+ }
+
+ JavaProcessLauncherImpl(File tempDir, AllProcessesCommands allProcessesCommands, Supplier<SystemProcessBuilder> processBuilderSupplier) {
this.tempDir = tempDir;
- this.allProcessesCommands = new AllProcessesCommands(tempDir);
+ this.allProcessesCommands = allProcessesCommands;
+ this.processBuilderSupplier = processBuilderSupplier;
}
+ @Override
public void close() {
allProcessesCommands.close();
}
- ProcessRef launch(JavaCommand command) {
+ @Override
+ public ProcessMonitor launch(JavaCommand javaCommand) {
Process process = null;
+ ProcessCommands commands;
try {
- ProcessCommands commands = allProcessesCommands.createAfterClean(command.getProcessId().getIpcIndex());
+ commands = allProcessesCommands.createAfterClean(javaCommand.getProcessId().getIpcIndex());
- ProcessBuilder processBuilder = create(command);
- LoggerFactory.getLogger(getClass()).info("Launch process[{}]: {}",
- command.getProcessId().getKey(), StringUtils.join(processBuilder.command(), " "));
+ SystemProcessBuilder processBuilder = create(javaCommand);
+ LOG.info("Launch process[{}]: {}", javaCommand.getProcessId().getKey(), String.join(" ", processBuilder.command()));
process = processBuilder.start();
- StreamGobbler inputGobbler = new StreamGobbler(process.getInputStream(), command.getProcessId().getKey());
- inputGobbler.start();
-
- return new ProcessRef(command.getProcessId().getKey(), commands, process, inputGobbler);
+ return new ProcessMonitorImpl(process, commands);
} catch (Exception e) {
// just in case
- ProcessUtils.sendKillSignal(process);
- throw new IllegalStateException("Fail to launch [" + command.getProcessId().getKey() + "]", e);
+ if (process != null) {
+ process.destroyForcibly();
+ }
+ throw new IllegalStateException(format("Fail to launch process [%s]", javaCommand.getProcessId().getKey()), e);
}
}
- private ProcessBuilder create(JavaCommand javaCommand) {
+ private SystemProcessBuilder create(JavaCommand javaCommand) {
List<String> commands = new ArrayList<>();
commands.add(buildJavaPath());
commands.addAll(javaCommand.getJavaOptions());
// TODO warning - does it work if temp dir contains a whitespace ?
- commands.add(String.format("-Djava.io.tmpdir=%s", tempDir.getAbsolutePath()));
- commands.add(getJmxAgentCommand());
+ // TODO move to JavaCommandFactory ?
+ commands.add(format("-Djava.io.tmpdir=%s", tempDir.getAbsolutePath()));
commands.addAll(buildClasspath(javaCommand));
commands.add(javaCommand.getClassName());
commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
- ProcessBuilder processBuilder = new ProcessBuilder();
+ SystemProcessBuilder processBuilder = processBuilderSupplier.get();
processBuilder.command(commands);
processBuilder.directory(javaCommand.getWorkDir());
processBuilder.environment().putAll(javaCommand.getEnvVariables());
@@ -94,22 +100,14 @@ class JavaProcessLauncher {
return processBuilder;
}
- /**
- * JVM option to enable the agent that allows inter-process communication through JMX without
- * opening new ports. The agent is available in JRE of OpenJDK/OracleJDK only.
- * @see ProcessEntryPoint
- */
- private static String getJmxAgentCommand() {
- return "-javaagent:" + System.getProperty("java.home") + File.separator + "lib" + File.separator + "management-agent.jar";
- }
-
- private String buildJavaPath() {
+ private static String buildJavaPath() {
String separator = System.getProperty("file.separator");
return new File(new File(System.getProperty("java.home")), "bin" + separator + "java").getAbsolutePath();
}
- private List<String> buildClasspath(JavaCommand javaCommand) {
- return Arrays.asList("-cp", StringUtils.join(javaCommand.getClasspath(), System.getProperty("path.separator")));
+ 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) {
@@ -120,10 +118,11 @@ class JavaProcessLauncher {
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, String.valueOf(timeouts.getTerminationTimeout()));
+ // FIXME is it the responsibility of child process to have this timeout (too) ?
+ props.setProperty(PROPERTY_TERMINATION_TIMEOUT, "60000");
props.setProperty(PROPERTY_SHARED_PATH, tempDir.getAbsolutePath());
try (OutputStream out = new FileOutputStream(propertiesFile)) {
- props.store(out, String.format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
+ props.store(out, format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
}
return propertiesFile;
} catch (Exception e) {
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
new file mode 100644
index 00000000000..4d185fa71cb
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/Lifecycle.java
@@ -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-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
new file mode 100644
index 00000000000..a7f6a7b92b5
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessEventListener.java
@@ -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-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
new file mode 100644
index 00000000000..384499b10c2
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLifecycleListener.java
@@ -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-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
new file mode 100644
index 00000000000..299abfefe2e
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitor.java
@@ -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.process;
+
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+
+public interface ProcessMonitor {
+
+ /**
+ * @see Process#getInputStream()
+ */
+ InputStream getInputStream();
+
+ /**
+ * 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 set the operational flag (in ipc shared memory)
+ */
+ boolean isOperational();
+
+ /**
+ * Send request to gracefully stop to the process (via ipc shared memory)
+ */
+ void askForStop();
+
+ /**
+ * Whether the process asked for a full restart (via ipc shared memory)
+ */
+ boolean 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.
+ */
+ void acknowledgeAskForRestart();
+
+}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitorImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitorImpl.java
new file mode 100644
index 00000000000..26b62f134ad
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessMonitorImpl.java
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.sonar.process.ProcessCommands;
+
+import static java.util.Objects.requireNonNull;
+
+class ProcessMonitorImpl implements ProcessMonitor {
+
+ private final Process process;
+ private final ProcessCommands commands;
+
+ ProcessMonitorImpl(Process process, ProcessCommands commands) {
+ this.process = requireNonNull(process, "process can't be null");
+ this.commands = requireNonNull(commands, "commands can't be null");
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return process.getInputStream();
+ }
+
+ @Override
+ public void closeStreams() {
+ closeQuietly(process.getInputStream());
+ closeQuietly(process.getOutputStream());
+ closeQuietly(process.getErrorStream());
+ }
+
+ @Override
+ public boolean isAlive() {
+ return process.isAlive();
+ }
+
+ @Override
+ public void destroyForcibly() {
+ process.destroyForcibly();
+ }
+
+ @Override
+ public void waitFor() throws InterruptedException {
+ process.waitFor();
+ }
+
+ @Override
+ public void waitFor(long timeout, TimeUnit unit) throws InterruptedException {
+ process.waitFor(timeout, unit);
+ }
+
+ @Override
+ public boolean isOperational() {
+ return commands.isOperational();
+ }
+
+ @Override
+ public void askForStop() {
+ commands.askForStop();
+ }
+
+ @Override
+ public boolean askedForRestart() {
+ return commands.askedForRestart();
+ }
+
+ @Override
+ public void acknowledgeAskForRestart() {
+ commands.acknowledgeAskForRestart();
+ }
+
+ private static void closeQuietly(@Nullable Closeable closeable) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (IOException ignored) {
+ // ignore
+ }
+ }
+
+}
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
new file mode 100644
index 00000000000..26f8d91713f
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/SQProcess.java
@@ -0,0 +1,262 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 gobbler;
+ 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.gobbler = new StreamGobbler(process.getInputStream(), processId.getKey());
+ this.gobbler.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 (gobbler != null) {
+ StreamGobbler.waitUntilFinish(gobbler);
+ gobbler.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
new file mode 100644
index 00000000000..2fef4abd2f3
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcher.java
@@ -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-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
new file mode 100644
index 00000000000..6c92a14a2e2
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
@@ -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.DefaultProcessCommands;
+import org.sonar.process.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/process/monitor/StreamGobbler.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StreamGobbler.java
index a179eb5e6ee..b920360c37b 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StreamGobbler.java
@@ -17,16 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.process;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
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
*/
@@ -49,7 +50,7 @@ public class StreamGobbler extends Thread {
@Override
public void run() {
- try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(is, UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
logger.info(line);
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
new file mode 100644
index 00000000000..26e8d8e27b1
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/package-info.java
@@ -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-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java
deleted file mode 100644
index adb32e1c8a9..00000000000
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.process.monitor;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Supplier;
-import javax.annotation.CheckForNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.DefaultProcessCommands;
-import org.sonar.process.Lifecycle;
-import org.sonar.process.Lifecycle.State;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessUtils;
-import org.sonar.process.SystemExit;
-
-import static java.util.Objects.requireNonNull;
-import static org.sonar.process.DefaultProcessCommands.reset;
-
-public class Monitor {
-
- private static final Logger LOG = LoggerFactory.getLogger(Monitor.class);
- private static final Timeouts TIMEOUTS = new Timeouts();
- private static final long WATCH_DELAY_MS = 500L;
-
- private static int restartorInstanceCounter = 0;
-
- private final int processNumber;
- private final FileSystem fileSystem;
- private final SystemExit systemExit;
- private final boolean watchForHardStop;
- private final Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook");
- private final boolean waitForOperational;
-
- private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<>();
- private final Lifecycle lifecycle;
-
- private final TerminatorThread terminator = new TerminatorThread();
- private final RestartRequestWatcherThread restartWatcher = new RestartRequestWatcherThread();
- @CheckForNull
- private Supplier<List<JavaCommand>> javaCommandsSupplier;
- @CheckForNull
- private List<JavaCommand> javaCommands;
- @CheckForNull
- private JavaProcessLauncher launcher;
- @CheckForNull
- private RestartorThread restartor;
- @CheckForNull
- HardStopWatcherThread hardStopWatcher;
-
- private Monitor(Builder builder) {
- this.processNumber = builder.processNumber;
- this.fileSystem = requireNonNull(builder.fileSystem, "FileSystem can't be null²");
- this.systemExit = builder.exit == null ? new SystemExit() : builder.exit;
- this.watchForHardStop = builder.watchForHardStop;
- this.waitForOperational = builder.waitForOperational;
- this.lifecycle = builder.listeners == null ? new Lifecycle() : new Lifecycle(builder.listeners.stream().toArray(Lifecycle.LifecycleListener[]::new));
- }
-
- public static Builder newMonitorBuilder() {
- return new Builder();
- }
-
- public static class Builder {
- private int processNumber;
- private FileSystem fileSystem;
- private SystemExit exit;
- private boolean watchForHardStop;
- private boolean waitForOperational = false;
- private List<Lifecycle.LifecycleListener> listeners;
-
- private Builder() {
- // use static factory method
- }
-
- public Builder setProcessNumber(int processNumber) {
- this.processNumber = processNumber;
- return this;
- }
-
- public Builder setFileSystem(FileSystem fileSystem) {
- this.fileSystem = fileSystem;
- return this;
- }
-
- public Builder setExit(SystemExit exit) {
- this.exit = exit;
- return this;
- }
-
- public Builder setWatchForHardStop(boolean watchForHardStop) {
- this.watchForHardStop = watchForHardStop;
- return this;
- }
-
- public Builder setWaitForOperational() {
- this.waitForOperational = true;
- return this;
- }
-
- public Builder addListener(Lifecycle.LifecycleListener listener) {
- if (this.listeners == null) {
- this.listeners = new ArrayList<>(1);
- }
- this.listeners.add(requireNonNull(listener, "LifecycleListener can't be null"));
- return this;
- }
-
- public Monitor build() {
- return new Monitor(this);
- }
- }
-
- /**
- * Starts commands and blocks current thread until all processes are in state {@link State#STARTED} (or
- * {@link State#OPERATIONAL} if {@link #waitForOperational} is {@code true}).
- *
- * @throws IllegalArgumentException if commands list is empty
- * @throws IllegalStateException if already started or if at least one process failed to start. In this case
- * all processes are terminated. No need to execute {@link #stop()}
- */
- public void start(Supplier<List<JavaCommand>> javaCommandsSupplier) throws InterruptedException {
- this.javaCommandsSupplier = javaCommandsSupplier;
- // load java commands now, to fail fast if need be
- List<JavaCommand> commands = loadJavaCommands();
-
- if (lifecycle.getState() != State.INIT) {
- throw new IllegalStateException("Can not start multiple times");
- }
-
- // intercepts CTRL-C
- Runtime.getRuntime().addShutdownHook(shutdownHook);
-
- // start watching for restart requested by child process
- restartWatcher.start();
-
- startProcesses(() -> commands);
- }
-
- /**
- * @throws IllegalArgumentException if supplied didn't provide at least one JavaCommand
- */
- private List<JavaCommand> loadJavaCommands() {
- List<JavaCommand> commands = this.javaCommandsSupplier.get();
- if (commands.isEmpty()) {
- throw new IllegalArgumentException("At least one command is required");
- }
- return commands;
- }
-
- /**
- * Starts the processes defined by the JavaCommand in {@link #javaCommands}/
- */
- private void startProcesses(Supplier<List<JavaCommand>> javaCommandsSupplier) throws InterruptedException {
- // do no start any child process if not in state INIT or RESTARTING (a stop could be in progress too)
- if (lifecycle.tryToMoveTo(State.STARTING)) {
- resetFileSystem();
-
- // start watching for stop requested by other process (eg. orchestrator) if enabled and not started yet
- if (watchForHardStop && hardStopWatcher == null) {
- hardStopWatcher = new HardStopWatcherThread();
- hardStopWatcher.start();
- }
-
- this.javaCommands = javaCommandsSupplier.get();
- startAndMonitorProcesses();
- stopIfAnyProcessDidNotStart();
- waitForOperationalProcesses();
- }
- }
-
- private void resetFileSystem() {
- // since JavaLauncher depends on temp directory, which is reset below, we need to close it first
- closeJavaLauncher();
- try {
- fileSystem.reset();
- } catch (IOException e) {
- // failed to reset FileSystem
- throw new RuntimeException("Failed to reset file system", e);
- }
- // reset sharedmemory of App
- reset(fileSystem.getTempDir(), ProcessId.APP.getIpcIndex());
- }
-
- private void closeJavaLauncher() {
- if (this.launcher != null) {
- this.launcher.close();
- this.launcher = null;
- }
- }
-
- private void startAndMonitorProcesses() throws InterruptedException {
- File tempDir = fileSystem.getTempDir();
- this.launcher = new JavaProcessLauncher(TIMEOUTS, tempDir);
- for (JavaCommand command : javaCommands) {
- ProcessRef processRef = null;
- try {
- processRef = launcher.launch(command);
- monitor(processRef);
- } catch (InterruptedException | RuntimeException e) {
- if (processRef != null) {
- LOG.error("{} failed to start", processRef);
- }
- // fail to start or to monitor
- stop();
- throw e;
- }
- }
- }
-
- private void monitor(ProcessRef processRef) throws InterruptedException {
- // physically watch if process is alive
- WatcherThread watcherThread = new WatcherThread(processRef, this);
- watcherThread.start();
- watcherThreads.add(watcherThread);
-
- // wait for process to be ready (accept requests or so on)
- processRef.waitForUp();
-
- LOG.info("{} is up", processRef);
- }
-
- private void stopIfAnyProcessDidNotStart() {
- if (!lifecycle.tryToMoveTo(State.STARTED)) {
- // stopping or stopped during startup, for instance :
- // 1. A is started
- // 2. B starts
- // 3. A crashes while B is starting
- // 4. if B was not monitored during Terminator execution, then it's an alive orphan
- stop();
- throw new IllegalStateException("Stopped during startup");
- }
- }
-
- private void waitForOperationalProcesses() throws InterruptedException {
- if (!waitForOperational) {
- return;
- }
-
- for (WatcherThread watcherThread : watcherThreads) {
- waitForOperationalProcess(watcherThread.getProcessRef());
- }
- lifecycle.tryToMoveTo(State.OPERATIONAL);
- }
-
- private static void waitForOperationalProcess(ProcessRef processRef) throws InterruptedException {
- LOG.debug("Waiting for {} to be operational", processRef);
- while (!processRef.getCommands().isOperational()) {
- Thread.sleep(WATCH_DELAY_MS);
- }
- LOG.debug("{} is operational", processRef);
- }
-
- /**
- * Blocks until all processes are terminated
- */
- public void awaitTermination() {
- while (awaitChildProcessesTermination()) {
- trace("await termination of restartor...");
- ProcessUtils.awaitTermination(restartor);
- }
- cleanAfterTermination();
- }
-
- boolean waitForOneRestart() {
- boolean restartRequested = awaitChildProcessesTermination();
- trace("finished waiting, restartRequested={}", restartRequested);
- if (restartRequested) {
- trace("awaitTermination restartor={}", restartor);
- ProcessUtils.awaitTermination(restartor);
- }
- return restartRequested;
- }
-
- private boolean awaitChildProcessesTermination() {
- trace("await termination of child processes...");
- List<WatcherThread> watcherThreadsCopy = new ArrayList<>(this.watcherThreads);
- for (WatcherThread watcherThread : watcherThreadsCopy) {
- ProcessUtils.awaitTermination(watcherThread);
- }
- trace("all child processes done");
- return hasRestartBeenRequested(watcherThreadsCopy);
- }
-
- private static boolean hasRestartBeenRequested(List<WatcherThread> watcherThreads) {
- for (WatcherThread watcherThread : watcherThreads) {
- if (watcherThread.isAskedForRestart()) {
- trace("one child process requested restart");
- return true;
- }
- }
- trace("no child process requested restart");
- return false;
- }
-
- /**
- * Blocks until all processes are terminated.
- */
- public void stop() {
- trace("start hard stop async...");
- stopAsync(State.HARD_STOPPING);
- trace("await termination of terminator...");
- ProcessUtils.awaitTermination(terminator);
- cleanAfterTermination();
- trace("exit...");
- systemExit.exit(0);
- }
-
- private void cleanAfterTermination() {
- trace("go to STOPPED...");
- if (lifecycle.tryToMoveTo(State.STOPPED)) {
- trace("await termination of restartWatcher and hardStopWatcher...");
- // wait for restartWatcher and hardStopWatcher to cleanly stop
- ProcessUtils.awaitTermination(restartWatcher, hardStopWatcher);
- trace("restartWatcher done");
- // removing shutdown hook to avoid called stop() unnecessarily unless already in shutdownHook
- if (!systemExit.isInShutdownHook()) {
- trace("removing shutdown hook...");
- Runtime.getRuntime().removeShutdownHook(shutdownHook);
- }
- // cleanly close JavaLauncher
- closeJavaLauncher();
- }
- }
-
- /**
- * Asks for processes termination and returns without blocking until termination.
- * However, if a termination request is already under way (it's not supposed to happen, but, technically, it can occur),
- * this call will be blocking until the previous request finishes.
- */
- public void stopAsync() {
- stopAsync(State.STOPPING);
- }
-
- private void stopAsync(State stoppingState) {
- assert stoppingState == State.STOPPING || stoppingState == State.HARD_STOPPING;
- if (lifecycle.tryToMoveTo(stoppingState)) {
- terminator.start();
- }
- }
-
- public void restartAsync() {
- if (lifecycle.tryToMoveTo(State.RESTARTING)) {
- restartor = new RestartorThread();
- restartor.start();
- }
- }
-
- /**
- * Runs every time a restart request is detected.
- */
- private class RestartorThread extends Thread {
-
- private RestartorThread() {
- super("Restartor " + (restartorInstanceCounter++));
- }
-
- @Override
- public void run() {
- stopProcesses();
- try {
- startProcesses(Monitor.this::loadJavaCommands);
- } catch (InterruptedException e) {
- // Startup was interrupted. Processes are being stopped asynchronously.
- // Restoring the interruption state.
- Thread.currentThread().interrupt();
- } catch (Throwable t) {
- LOG.error("Restart failed", t);
- stopAsync(Lifecycle.State.HARD_STOPPING);
- }
- }
- }
-
- /**
- * Runs only once
- */
- private class TerminatorThread extends Thread {
-
- private TerminatorThread() {
- super("Terminator");
- }
-
- @Override
- public void run() {
- stopProcesses();
- }
- }
-
- /**
- * Watches for any child process requesting a restart of all children processes.
- * It runs once and as long as {@link #lifecycle} hasn't reached {@link Lifecycle.State#STOPPED} and holds its checks
- * when {@link #lifecycle} is not in state {@link Lifecycle.State#STARTED} to avoid taking the same request into account
- * twice.
- */
- public class RestartRequestWatcherThread extends Thread {
- public RestartRequestWatcherThread() {
- super("Restart watcher");
- }
-
- @Override
- public void run() {
- while (lifecycle.getState() != Lifecycle.State.STOPPED) {
- Lifecycle.State state = lifecycle.getState();
- if ((state == Lifecycle.State.STARTED || state == Lifecycle.State.OPERATIONAL) && didAnyProcessRequestRestart()) {
- restartAsync();
- }
- try {
- Thread.sleep(WATCH_DELAY_MS);
- } catch (InterruptedException ignored) {
- // keep watching
- }
- }
- }
-
- private boolean didAnyProcessRequestRestart() {
- for (WatcherThread watcherThread : watcherThreads) {
- ProcessRef processRef = watcherThread.getProcessRef();
- if (processRef.getCommands().askedForRestart()) {
- LOG.info("Process [{}] requested restart", processRef.getKey());
- return true;
- }
- }
- return false;
- }
-
- }
-
- public class HardStopWatcherThread extends Thread {
-
- public HardStopWatcherThread() {
- super("Hard stop watcher");
- }
-
- @Override
- public void run() {
- while (lifecycle.getState() != Lifecycle.State.STOPPED) {
- if (askedForStop()) {
- trace("Stopping process");
- Monitor.this.stop();
- } else {
- delay();
- }
- }
- }
-
- private boolean askedForStop() {
- File tempDir = fileSystem.getTempDir();
- try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tempDir, processNumber)) {
- if (processCommands.askedForStop()) {
- return true;
- }
- }
- return false;
- }
-
- private void delay() {
- try {
- Thread.sleep(WATCH_DELAY_MS);
- } catch (InterruptedException ignored) {
- // keep watching
- }
- }
-
- }
-
- private void stopProcesses() {
- List<WatcherThread> watcherThreadsCopy = new ArrayList<>(this.watcherThreads);
- // create a copy and reverse it to terminate in reverse order of startup (dependency order)
- Collections.reverse(watcherThreadsCopy);
-
- for (WatcherThread watcherThread : watcherThreadsCopy) {
- ProcessRef ref = watcherThread.getProcessRef();
- if (!ref.isStopped()) {
- LOG.info("{} is stopping", ref);
- ref.askForGracefulAsyncStop();
-
- long killAt = System.currentTimeMillis() + TIMEOUTS.getTerminationTimeout();
- while (!ref.isStopped() && System.currentTimeMillis() < killAt) {
- try {
- Thread.sleep(10L);
- } catch (InterruptedException e) {
- // stop asking for graceful stops, Monitor will hardly kill all processes
- break;
- }
- }
- if (!ref.isStopped()) {
- LOG.info("{} failed to stop in a timely fashion. Killing it.", ref);
- }
- ref.stop();
- LOG.info("{} is stopped", ref);
- }
- }
-
- // all processes are stopped, no need to keep references to these WatcherThread anymore
- trace("all processes stopped, clean list of watcherThreads...");
- this.watcherThreads.clear();
- }
-
- public State getState() {
- return lifecycle.getState();
- }
-
- Thread getShutdownHook() {
- return shutdownHook;
- }
-
- private class MonitorShutdownHook implements Runnable {
- @Override
- public void run() {
- systemExit.setInShutdownHook();
- trace("calling stop from MonitorShutdownHook...");
- // blocks until everything is corrected terminated
- stop();
- }
- }
-
- private static void trace(String s) {
- LOG.trace(s);
- }
-
- private static void trace(String s, Object args) {
- LOG.trace(s, args);
- }
-
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java
deleted file mode 100644
index 3a6c74f4360..00000000000
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.process.monitor;
-
-import org.slf4j.LoggerFactory;
-import org.sonar.process.MessageException;
-import org.sonar.process.ProcessCommands;
-import org.sonar.process.ProcessUtils;
-
-class ProcessRef {
-
- private final String key;
- private final ProcessCommands commands;
- private final Process process;
- private final StreamGobbler gobbler;
- private volatile boolean stopped = false;
-
- ProcessRef(String key, ProcessCommands commands, Process process, StreamGobbler gobbler) {
- this.key = key;
- this.commands = commands;
- this.process = process;
- this.stopped = !ProcessUtils.isAlive(process);
- this.gobbler = gobbler;
- }
-
- /**
- * Unique logical key (not the pid), for instance "ES"
- */
- String getKey() {
- return key;
- }
-
- /**
- * The {@link java.lang.Process}
- */
- Process getProcess() {
- return process;
- }
-
- public ProcessCommands getCommands() {
- return commands;
- }
-
- void waitForUp() throws InterruptedException {
- boolean up = false;
- while (!up) {
- if (isStopped()) {
- throw new MessageException(String.format("%s failed to start", this));
- }
- up = commands.isUp();
- Thread.sleep(200L);
- }
- }
-
- /**
- * True if process is physically down
- */
- boolean isStopped() {
- return stopped;
- }
-
- void askForGracefulAsyncStop() {
- commands.askForStop();
- }
-
- /**
- * Sends kill signal and awaits termination. No guarantee that process is gracefully terminated (=shutdown hooks
- * executed). It depends on OS.
- */
- void stop() {
- if (ProcessUtils.isAlive(process)) {
- try {
- ProcessUtils.sendKillSignal(process);
- // signal is sent, waiting for shutdown hooks to be executed (or not... it depends on OS)
- process.waitFor();
-
- } catch (InterruptedException e) {
- // can't wait for the termination of process. Let's assume it's down.
- LoggerFactory.getLogger(getClass()).warn(String.format("Interrupted while stopping process %s", key), e);
- Thread.currentThread().interrupt();
- }
- }
- ProcessUtils.closeStreams(process);
- StreamGobbler.waitUntilFinish(gobbler);
- stopped = true;
- }
-
- @Override
- public String toString() {
- return String.format("Process[%s]", key);
- }
-}
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java
deleted file mode 100644
index 6e855107d6d..00000000000
--- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java
+++ /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.process.monitor;
-
-/**
- * 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>
- */
-class WatcherThread extends Thread {
-
- private final ProcessRef processRef;
- private final Monitor monitor;
- private boolean askedForRestart = false;
-
- WatcherThread(ProcessRef processRef, Monitor monitor) {
- // this name is different than Thread#toString(), which includes name, priority
- // and thread group
- // -> do not override toString()
- super(String.format("Watch[%s]", processRef.getKey()));
- this.processRef = processRef;
- this.monitor = monitor;
- }
-
- @Override
- public void run() {
- boolean stopped = false;
- while (!stopped) {
- try {
- processRef.getProcess().waitFor();
- askedForRestart = processRef.getCommands().askedForRestart();
- processRef.getCommands().acknowledgeAskForRestart();
-
- // finalize status of ProcessRef
- processRef.stop();
-
- // terminate all other processes, but in another thread
- monitor.stopAsync();
- stopped = true;
- } catch (InterruptedException ignored) {
- // continue to watch process
- }
- }
- }
-
- public ProcessRef getProcessRef() {
- return processRef;
- }
-
- public boolean isAskedForRestart() {
- return askedForRestart;
- }
-}
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
new file mode 100644
index 00000000000..16c11d89946
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java
@@ -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.AllProcessesCommands;
+import org.sonar.process.ProcessProperties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.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/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppLoggingTest.java
index 94b3ad37564..61b5feb26db 100644
--- a/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppLoggingTest.java
@@ -33,7 +33,6 @@ import ch.qos.logback.core.rolling.RollingFileAppender;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
-import java.util.Properties;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
@@ -41,13 +40,14 @@ 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.Props;
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.process.monitor.StreamGobbler.LOGGER_GOBBLER;
+import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
public class AppLoggingTest {
@@ -58,13 +58,13 @@ public class AppLoggingTest {
private File logDir;
- private Props props = new Props(new Properties());
- private AppLogging underTest = new AppLogging();
+ private AppSettings settings = new TestAppSettings();
+ private AppLogging underTest = new AppLogging(settings);
@Before
public void setUp() throws Exception {
logDir = temp.newFolder();
- props.set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
+ settings.getProps().set(ProcessProperties.PATH_LOGS, logDir.getAbsolutePath());
}
@AfterClass
@@ -76,7 +76,7 @@ public class AppLoggingTest {
public void no_writing_to_sonar_log_file_when_running_from_sonar_script() {
emulateRunFromSonarScript();
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
ctx.getLoggerList().forEach(AppLoggingTest::verifyNoFileAppender);
}
@@ -85,7 +85,7 @@ public class AppLoggingTest {
public void root_logger_only_writes_to_console_with_formatting_when_running_from_sonar_script() {
emulateRunFromSonarScript();
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) rootLogger.getAppender("APP_CONSOLE");
@@ -97,7 +97,7 @@ public class AppLoggingTest {
public void gobbler_logger_writes_to_console_without_formatting_when_running_from_sonar_script() {
emulateRunFromSonarScript();
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
verifyGobblerConsoleAppender(gobblerLogger);
@@ -108,7 +108,7 @@ public class AppLoggingTest {
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(props);
+ LoggerContext ctx = underTest.configure();
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
verifyAppConsoleAppender(rootLogger.getAppender("APP_CONSOLE"));
@@ -126,7 +126,7 @@ public class AppLoggingTest {
public void gobbler_logger_writes_to_console_without_formatting_when_running_from_command_line() {
emulateRunFromCommandLine(false);
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
verifyGobblerConsoleAppender(gobblerLogger);
@@ -137,7 +137,7 @@ public class AppLoggingTest {
public void root_logger_writes_to_console_with_formatting_and_to_sonar_log_file_when_running_from_ITs() {
emulateRunFromCommandLine(true);
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
verifyAppConsoleAppender(rootLogger.getAppender("APP_CONSOLE"));
@@ -154,7 +154,7 @@ public class AppLoggingTest {
public void gobbler_logger_writes_to_console_without_formatting_when_running_from_ITs() {
emulateRunFromCommandLine(true);
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER);
verifyGobblerConsoleAppender(gobblerLogger);
@@ -163,9 +163,9 @@ public class AppLoggingTest {
@Test
public void configure_no_rotation_on_sonar_file() {
- props.set("sonar.log.rollingPolicy", "none");
+ settings.getProps().set("sonar.log.rollingPolicy", "none");
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
Appender<ILoggingEvent> appender = rootLogger.getAppender("file_sonar");
@@ -176,108 +176,104 @@ public class AppLoggingTest {
@Test
public void default_level_for_root_logger_is_INFO() {
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
verifyRootLogLevel(ctx, Level.INFO);
}
@Test
public void root_logger_level_changes_with_global_property() {
- props.set("sonar.log.level", "TRACE");
+ settings.getProps().set("sonar.log.level", "TRACE");
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
verifyRootLogLevel(ctx, Level.TRACE);
}
@Test
public void root_logger_level_changes_with_app_property() {
- props.set("sonar.log.level.app", "TRACE");
+ settings.getProps().set("sonar.log.level.app", "TRACE");
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
verifyRootLogLevel(ctx, Level.TRACE);
}
@Test
public void root_logger_level_is_configured_from_app_property_over_global_property() {
- props.set("sonar.log.level", "TRACE");
- props.set("sonar.log.level.app", "DEBUG");
+ settings.getProps().set("sonar.log.level", "TRACE");
+ settings.getProps().set("sonar.log.level.app", "DEBUG");
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
verifyRootLogLevel(ctx, Level.DEBUG);
}
@Test
public void root_logger_level_changes_with_app_property_and_is_case_insensitive() {
- props.set("sonar.log.level.app", "debug");
+ settings.getProps().set("sonar.log.level.app", "debug");
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
verifyRootLogLevel(ctx, Level.DEBUG);
}
@Test
public void default_to_INFO_if_app_property_has_invalid_value() {
- props.set("sonar.log.level.app", "DodoDouh!");
+ settings.getProps().set("sonar.log.level.app", "DodoDouh!");
- LoggerContext ctx = underTest.configure(props);
+ LoggerContext ctx = underTest.configure();
verifyRootLogLevel(ctx, Level.INFO);
}
@Test
public void fail_with_IAE_if_global_property_unsupported_level() {
- props.set("sonar.log.level", "ERROR");
+ 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(props);
+ underTest.configure();
}
@Test
public void fail_with_IAE_if_app_property_unsupported_level() {
- props.set("sonar.log.level.app", "ERROR");
+ 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(props);
+ underTest.configure();
}
@Test
public void no_info_log_from_hazelcast() throws IOException {
- props.set(ClusterParameters.ENABLED.getName(), "true");
- new ClusterProperties(props).populateProps(props);
- underTest.configure(props);
+ settings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+ underTest.configure();
assertThat(
- LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()
- ).isEqualTo(false);
+ LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(false);
}
@Test
public void configure_logging_for_hazelcast() throws IOException {
- props.set(ClusterParameters.ENABLED.getName(), "true");
- props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), "INFO");
- underTest.configure(props);
+ 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);
+ LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(true);
assertThat(
- LoggerFactory.getLogger("com.hazelcast").isDebugEnabled()
- ).isEqualTo(false);
+ LoggerFactory.getLogger("com.hazelcast").isDebugEnabled()).isEqualTo(false);
}
private void emulateRunFromSonarScript() {
- props.set("sonar.wrapped", "true");
+ settings.getProps().set("sonar.wrapped", "true");
}
private void emulateRunFromCommandLine(boolean withAllLogsPrintedToConsole) {
if (withAllLogsPrintedToConsole) {
- props.set("sonar.log.console", "true");
+ settings.getProps().set("sonar.log.console", "true");
}
}
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
new file mode 100644
index 00000000000..972720a6c7e
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppReloaderImplTest.java
@@ -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-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java
new file mode 100644
index 00000000000..ffffcc00855
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateFactoryTest.java
@@ -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-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java
new file mode 100644
index 00000000000..e49c5f54758
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppStateImplTest.java
@@ -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-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java
new file mode 100644
index 00000000000..ddc2c587281
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/SchedulerImplTest.java
@@ -0,0 +1,443 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.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.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.application.config.TestAppSettings;
+import org.sonar.application.process.JavaCommand;
+import org.sonar.application.process.JavaCommandFactory;
+import org.sonar.application.process.JavaProcessLauncher;
+import org.sonar.application.process.ProcessMonitor;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+
+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 {
+
+ private static final JavaCommand ES_COMMAND = new JavaCommand(ELASTICSEARCH);
+ private static final JavaCommand WEB_LEADER_COMMAND = new JavaCommand(WEB_SERVER);
+ private static final JavaCommand WEB_FOLLOWER_COMMAND = new JavaCommand(WEB_SERVER);
+ private static final JavaCommand CE_COMMAND = new JavaCommand(COMPUTE_ENGINE);
+
+ @Rule
+ public TestRule safeGuard = new DisableOnDebug(Timeout.seconds(10));
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private AppReloader appReloader = mock(AppReloader.class);
+ private TestAppSettings settings = new TestAppSettings();
+ private TestJavaCommandFactory javaCommandFactory = new TestJavaCommandFactory();
+ private TestJavaProcessLauncher processLauncher = new TestJavaProcessLauncher();
+ private TestAppState appState = new TestAppState();
+ private List<ProcessId> orderedStops = new ArrayList<>();
+
+ @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(ES_COMMAND, WEB_LEADER_COMMAND);
+
+ // 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(ES_COMMAND, WEB_LEADER_COMMAND, CE_COMMAND);
+
+ // 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
+ 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(10_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 static class TestJavaCommandFactory implements JavaCommandFactory {
+ @Override
+ public JavaCommand createEsCommand() {
+ return ES_COMMAND;
+ }
+
+ @Override
+ public JavaCommand createWebCommand(boolean leader) {
+ return leader ? WEB_LEADER_COMMAND : WEB_FOLLOWER_COMMAND;
+ }
+
+ @Override
+ public JavaCommand createCeCommand() {
+ return CE_COMMAND;
+ }
+ }
+
+ private class TestJavaProcessLauncher implements JavaProcessLauncher {
+ private final EnumMap<ProcessId, TestProcess> processes = new EnumMap<>(ProcessId.class);
+ private final List<JavaCommand> commands = new ArrayList<>();
+ private ProcessId makeStartupFail = null;
+
+ @Override
+ public ProcessMonitor launch(JavaCommand 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 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
new file mode 100644
index 00000000000..e69624382eb
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nonnull;
+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 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
new file mode 100644
index 00000000000..a1be5f33895
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java
@@ -0,0 +1,125 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 com.hazelcast.core.ReplicatedMap;
+import java.net.InetAddress;
+import java.util.UUID;
+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.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.Java6Assertions.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.AppStateClusterImpl.OPERATIONAL_PROCESSES;
+
+public class AppStateClusterImplTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public TestRule safeGuard = new DisableOnDebug(Timeout.seconds(10));
+
+ @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 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 simulate_network_cluster() throws InterruptedException {
+ TestAppSettings settings = newClusterSettings();
+ settings.set(ProcessProperties.CLUSTER_INTERFACES, InetAddress.getLoopbackAddress().getHostAddress());
+ AppStateListener listener = mock(AppStateListener.class);
+
+ try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+ appStateCluster.addListener(listener);
+
+ HazelcastInstance hzInstance = HazelcastHelper.createHazelcastClient(appStateCluster);
+ 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();
+ }
+ }
+
+ private 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/cluster/ClusterPropertiesTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java
new file mode 100644
index 00000000000..c974219839d
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java
@@ -0,0 +1,136 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.getInterfaces())
+ .isEqualTo(Collections.emptyList());
+ assertThat(props.getPort())
+ .isEqualTo(9003);
+ assertThat(props.isEnabled())
+ .isEqualTo(false);
+ assertThat(props.getMembers())
+ .isEqualTo(Collections.emptyList());
+ assertThat(props.getName())
+ .isEqualTo("");
+ }
+
+ @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_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 test_missing_name() {
+ appSettings.getProps().set(ProcessProperties.CLUSTER_ENABLED, "true");
+ appSettings.getProps().set(ProcessProperties.CLUSTER_NAME, "");
+
+ ClusterProperties clusterProperties = new ClusterProperties(appSettings);
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(
+ String.format("Cluster have been enabled but a %s has not been defined.",
+ ProcessProperties.CLUSTER_NAME));
+ 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).getMembers()).isEqualTo(
+ Collections.emptyList());
+
+ appSettings.getProps().set(ProcessProperties.CLUSTER_MEMBERS, "192.168.1.1");
+ assertThat(
+ new ClusterProperties(appSettings).getMembers()).isEqualTo(
+ Arrays.asList("192.168.1.1:9003"));
+
+ appSettings.getProps().set(ProcessProperties.CLUSTER_MEMBERS, "192.168.1.2:5501");
+ assertThat(
+ new ClusterProperties(appSettings).getMembers()).containsExactlyInAnyOrder(
+ "192.168.1.2:5501");
+
+ appSettings.getProps().set(ProcessProperties.CLUSTER_MEMBERS, "192.168.1.2:5501,192.168.1.1");
+ assertThat(
+ new ClusterProperties(appSettings).getMembers()).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/HazelcastHelper.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastHelper.java
new file mode 100644
index 00000000000..9cfe2559e67
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastHelper.java
@@ -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.cluster;
+
+import com.hazelcast.client.HazelcastClient;
+import com.hazelcast.client.config.ClientConfig;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.config.NetworkConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+
+public class HazelcastHelper {
+ static HazelcastInstance createHazelcastNode(AppStateClusterImpl appStateCluster) {
+ Config hzConfig = new Config()
+ .setInstanceName(appStateCluster.hzInstance.getName() + "_1");
+
+ // Configure the network instance
+ NetworkConfig netConfig = hzConfig.getNetworkConfig();
+ netConfig.setPort(9003).setPortAutoIncrement(true);
+ Collection<String> interfaces = appStateCluster.hzInstance.getConfig().getNetworkConfig().getInterfaces().getInterfaces();
+ if (!interfaces.isEmpty()) {
+ netConfig.getInterfaces().addInterface(
+ interfaces.iterator().next()
+ );
+ }
+
+ // Only allowing TCP/IP configuration
+ JoinConfig joinConfig = netConfig.getJoin();
+ joinConfig.getAwsConfig().setEnabled(false);
+ joinConfig.getMulticastConfig().setEnabled(false);
+ joinConfig.getTcpIpConfig().setEnabled(true);
+
+ InetSocketAddress socketAddress = (InetSocketAddress) appStateCluster.hzInstance.getLocalEndpoint().getSocketAddress();
+ joinConfig.getTcpIpConfig().addMember(
+ String.format("%s:%d",
+ socketAddress.getHostString(),
+ socketAddress.getPort()
+ )
+ );
+
+ // 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");
+
+ // We are not using the partition group of Hazelcast, so disabling it
+ hzConfig.getPartitionGroupConfig().setEnabled(false);
+
+ return Hazelcast.newHazelcastInstance(hzConfig);
+ }
+
+ static HazelcastInstance createHazelcastClient(AppStateClusterImpl appStateCluster) {
+ ClientConfig clientConfig = new ClientConfig();
+ InetSocketAddress socketAddress = (InetSocketAddress) appStateCluster.hzInstance.getLocalEndpoint().getSocketAddress();
+
+ clientConfig.getNetworkConfig().getAddresses().add(
+ String.format("%s:%d",
+ socketAddress.getHostString(),
+ socketAddress.getPort()
+ ));
+ clientConfig.getGroupConfig().setName(appStateCluster.hzInstance.getConfig().getGroupConfig().getName());
+ return HazelcastClient.newHazelcastClient(clientConfig);
+ }
+}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsImplTest.java
index e91afaedb89..2ea6215052e 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsImplTest.java
@@ -17,23 +17,29 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.config;
+import java.util.Properties;
import org.junit.Test;
-import org.mockito.Mockito;
+import org.sonar.process.Props;
-import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThat;
-public class WatcherThreadTest {
+public class AppSettingsImplTest {
- @Test(timeout = 10000L)
- public void continue_even_if_interrupted() throws Exception {
- Monitor monitor = mock(Monitor.class);
- ProcessRef ref = mock(ProcessRef.class, Mockito.RETURNS_DEEP_STUBS);
- when(ref.getProcess().waitFor()).thenThrow(new InterruptedException()).thenReturn(0);
- WatcherThread watcher = new WatcherThread(ref, monitor);
- watcher.start();
- watcher.join();
- verify(monitor).stopAsync();
+ @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
new file mode 100644
index 00000000000..d1337a8c6bc
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/AppSettingsLoaderImplTest.java
@@ -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-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
new file mode 100644
index 00000000000..079cbf49b6a
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessProperties;
+
+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;
+
+public class ClusterSettingsTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private TestAppSettings settings = new TestAppSettings();
+
+ @Test
+ public void test_isClusterEnabled() {
+ settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+ assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue();
+
+ settings.set(ProcessProperties.CLUSTER_ENABLED, "false");
+ assertThat(ClusterSettings.isClusterEnabled(settings)).isFalse();
+ }
+
+ @Test
+ public void isClusterEnabled_returns_false_by_default() {
+ assertThat(ClusterSettings.isClusterEnabled(settings)).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(ProcessProperties.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(ProcessProperties.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(ProcessProperties.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_does_nothing_if_cluster_is_disabled() {
+ settings.set(ProcessProperties.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(ProcessProperties.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.set(ProcessProperties.CLUSTER_ENABLED, "true");
+
+ 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() {
+ settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+
+ assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
+ }
+
+ @Test
+ public void isLocalElasticsearchEnabled_returns_false_if_local_es_node_is_disabled_in_cluster_mode() {
+ settings.set(ProcessProperties.CLUSTER_ENABLED, "true");
+ settings.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
+
+ assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse();
+ }
+}
diff --git a/sonar-application/src/test/java/org/sonar/application/CommandLineParserTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/CommandLineParserTest.java
index e8c915bf587..d94851e0cfc 100644
--- a/sonar-application/src/test/java/org/sonar/application/CommandLineParserTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/CommandLineParserTest.java
@@ -17,25 +17,26 @@
* 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;
+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;
-import static org.junit.Assert.fail;
public class CommandLineParserTest {
- CommandLineParser parser = new CommandLineParser();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
@Test
public void parseArguments() {
System.setProperty("CommandLineParserTest.unused", "unused");
System.setProperty("sonar.CommandLineParserTest.used", "used");
- Properties p = parser.parseArguments(new String[] {"-Dsonar.foo=bar"});
+ 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"
@@ -46,17 +47,15 @@ public class CommandLineParserTest {
}
@Test
- public void argumentsToProperties() {
- Properties p = parser.argumentsToProperties(new String[] {"-Dsonar.foo=bar", "-Dsonar.whitespace=foo bar"});
+ 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");
- try {
- parser.argumentsToProperties(new String[] {"-Dsonar.foo=bar", "sonar.bad=true"});
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Command-line argument must start with -D, for example -Dsonar.jdbc.username=sonar. Got: sonar.bad=true");
- }
+ 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
new file mode 100644
index 00000000000..dd902f0d5b4
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/FileSystemSettingsTest.java
@@ -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/sonar-application/src/test/java/org/sonar/application/JdbcSettingsTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/JdbcSettingsTest.java
index 8722c5242d3..45510230779 100644
--- a/sonar-application/src/test/java/org/sonar/application/JdbcSettingsTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/JdbcSettingsTest.java
@@ -17,11 +17,12 @@
* 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;
+package org.sonar.application.config;
import java.io.File;
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;
@@ -31,7 +32,7 @@ import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.application.JdbcSettings.Provider;
+import static org.sonar.application.config.JdbcSettings.Provider;
import static org.sonar.process.ProcessProperties.JDBC_URL;
public class JdbcSettingsTest {
@@ -41,13 +42,19 @@ public class JdbcSettingsTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
- JdbcSettings settings = new JdbcSettings();
+ 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(settings.resolveProviderAndEnforceNonnullJdbcUrl(props))
+ assertThat(underTest.resolveProviderAndEnforceNonnullJdbcUrl(props))
.isEqualTo(Provider.H2);
assertThat(props.nonNullValue(JDBC_URL)).isEqualTo("jdbc:h2:tcp://localhost:9092/sonar");
}
@@ -82,7 +89,7 @@ public class JdbcSettingsTest {
private void checkProviderForUrlAndUnchangedUrl(String url, Provider expected) {
Props props = newProps(JDBC_URL, url);
- assertThat(settings.resolveProviderAndEnforceNonnullJdbcUrl(props)).isEqualTo(expected);
+ assertThat(underTest.resolveProviderAndEnforceNonnullJdbcUrl(props)).isEqualTo(expected);
assertThat(props.nonNullValue(JDBC_URL)).isEqualTo(url);
}
@@ -93,7 +100,7 @@ public class JdbcSettingsTest {
expectedException.expect(MessageException.class);
expectedException.expectMessage("Unsupported JDBC driver provider: microsoft");
- settings.resolveProviderAndEnforceNonnullJdbcUrl(props);
+ underTest.resolveProviderAndEnforceNonnullJdbcUrl(props);
}
@Test
@@ -103,111 +110,102 @@ public class JdbcSettingsTest {
expectedException.expect(MessageException.class);
expectedException.expectMessage("Bad format of JDBC URL: oracle:thin:@localhost/XE");
- settings.resolveProviderAndEnforceNonnullJdbcUrl(props);
+ underTest.resolveProviderAndEnforceNonnullJdbcUrl(props);
}
@Test
public void check_mysql_parameters() {
// minimal -> ok
- settings.checkUrlParameters(Provider.MYSQL,
+ underTest.checkUrlParameters(Provider.MYSQL,
"jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8");
// full -> ok
- settings.checkUrlParameters(Provider.MYSQL,
+ 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'");
- settings.checkUrlParameters(Provider.MYSQL, "jdbc:mysql://localhost:3306/sonar?characterEncoding=utf8");
+ underTest.checkUrlParameters(Provider.MYSQL, "jdbc:mysql://localhost:3306/sonar?characterEncoding=utf8");
}
@Test
public void checkAndComplete_sets_driver_path_for_oracle() throws Exception {
- File home = temp.newFolder();
- File driverFile = new File(home, "extensions/jdbc-driver/oracle/ojdbc6.jar");
+ File driverFile = new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar");
FileUtils.touch(driverFile);
Props props = newProps(JDBC_URL, "jdbc:oracle:thin:@localhost/XE");
- settings.checkAndComplete(home, props);
+ underTest.accept(props);
assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
}
@Test
- public void checkAndComplete_sets_driver_path_for_h2() throws Exception {
- File home = temp.newFolder();
- File driverFile = new File(home, "lib/jdbc/h2/h2.jar");
+ 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");
- settings.checkAndComplete(home, props);
+ underTest.accept(props);
assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
}
@Test
public void checkAndComplete_sets_driver_path_for_postgresql() throws Exception {
- File home = temp.newFolder();
- File driverFile = new File(home, "lib/jdbc/postgresql/pg.jar");
+ File driverFile = new File(homeDir, "lib/jdbc/postgresql/pg.jar");
FileUtils.touch(driverFile);
Props props = newProps(JDBC_URL, "jdbc:postgresql://localhost/sonar");
- settings.checkAndComplete(home, props);
+ underTest.accept(props);
assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
}
@Test
public void checkAndComplete_sets_driver_path_for_mssql() throws Exception {
- File home = temp.newFolder();
- File driverFile = new File(home, "lib/jdbc/mssql/sqljdbc4.jar");
+ File driverFile = new File(homeDir, "lib/jdbc/mssql/sqljdbc4.jar");
FileUtils.touch(driverFile);
Props props = newProps(JDBC_URL, "jdbc:sqlserver://localhost/sonar;SelectMethod=Cursor");
- settings.checkAndComplete(home, props);
+ underTest.accept(props);
assertThat(props.nonNullValueAsFile(ProcessProperties.JDBC_DRIVER_PATH)).isEqualTo(driverFile);
}
@Test
public void driver_file() throws Exception {
- File home = temp.newFolder();
- File driverFile = new File(home, "extensions/jdbc-driver/oracle/ojdbc6.jar");
+ File driverFile = new File(homeDir, "extensions/jdbc-driver/oracle/ojdbc6.jar");
FileUtils.touch(driverFile);
- String path = settings.driverPath(home, Provider.ORACLE);
+ String path = underTest.driverPath(homeDir, Provider.ORACLE);
assertThat(path).isEqualTo(driverFile.getAbsolutePath());
}
@Test
public void driver_dir_does_not_exist() throws Exception {
- File home = temp.newFolder();
-
expectedException.expect(MessageException.class);
expectedException.expectMessage("Directory does not exist: extensions/jdbc-driver/oracle");
- settings.driverPath(home, Provider.ORACLE);
+ underTest.driverPath(homeDir, Provider.ORACLE);
}
@Test
public void no_files_in_driver_dir() throws Exception {
- File home = temp.newFolder();
- FileUtils.forceMkdir(new File(home, "extensions/jdbc-driver/oracle"));
+ 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");
- settings.driverPath(home, Provider.ORACLE);
+ underTest.driverPath(homeDir, Provider.ORACLE);
}
@Test
public void too_many_files_in_driver_dir() throws Exception {
- File home = temp.newFolder();
- FileUtils.touch(new File(home, "extensions/jdbc-driver/oracle/ojdbc5.jar"));
- FileUtils.touch(new File(home, "extensions/jdbc-driver/oracle/ojdbc6.jar"));
+ 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");
- settings.driverPath(home, Provider.ORACLE);
+ underTest.driverPath(homeDir, Provider.ORACLE);
}
private Props newProps(String... params) {
@@ -216,6 +214,7 @@ public class JdbcSettingsTest {
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/TestAppSettings.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/TestAppSettings.java
new file mode 100644
index 00000000000..58971dd04b9
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/TestAppSettings.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 properties;
+
+ public TestAppSettings() {
+ this.properties = new Props(new Properties());
+ ProcessProperties.completeDefaults(this.properties);
+ }
+
+ public TestAppSettings set(String key, String value) {
+ this.properties.set(key, value);
+ return this;
+ }
+
+ @Override
+ public Props getProps() {
+ return properties;
+ }
+
+ @Override
+ public Optional<String> getValue(String key) {
+ return Optional.ofNullable(properties.value(key));
+ }
+
+ @Override
+ public void reload(Props copy) {
+ this.properties = copy;
+ }
+}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaCommandTest.java
index b672afe2c15..4d8d1f85c99 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaCommandTest.java
@@ -17,14 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.process;
+import java.io.File;
+import java.util.Properties;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.util.Properties;
import org.sonar.process.ProcessId;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,7 +34,7 @@ public class JavaCommandTest {
public TemporaryFolder temp = new TemporaryFolder();
@Test
- public void test_parameters() throws Exception {
+ public void test_command_with_complete_information() throws Exception {
JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
command.setArgument("first_arg", "val1");
@@ -63,7 +62,7 @@ public class JavaCommandTest {
}
@Test
- public void add_java_options() {
+ public void addJavaOptions_adds_jvm_options() {
JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
assertThat(command.getJavaOptions()).isEmpty();
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaProcessLauncherImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaProcessLauncherImplTest.java
new file mode 100644
index 00000000000..3beb208ec25
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/JavaProcessLauncherImplTest.java
@@ -0,0 +1,166 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.application.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.application.process.JavaCommand;
+import org.sonar.application.process.JavaProcessLauncher;
+import org.sonar.application.process.JavaProcessLauncherImpl;
+import org.sonar.application.process.ProcessMonitor;
+import org.sonar.process.AllProcessesCommands;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Java6Assertions.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 JavaProcessLauncherImplTest {
+
+ @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();
+ JavaProcessLauncher underTest = new JavaProcessLauncherImpl(tempDir, commands, () -> processBuilder);
+ JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
+ command.addClasspath("lib/*.class");
+ command.addClasspath("lib/*.jar");
+ command.setArgument("foo", "bar");
+ command.setClassName("org.sonarqube.Main");
+ command.setEnvVariable("VAR1", "valueOfVar1");
+ command.setWorkDir(temp.newFolder());
+
+ ProcessMonitor monitor = underTest.launch(command);
+
+ assertThat(monitor).isNotNull();
+ assertThat(processBuilder.started).isTrue();
+ assertThat(processBuilder.commands.get(0)).endsWith("java");
+ assertThat(processBuilder.commands).containsSequence(
+ "-Djava.io.tmpdir=" + tempDir.getAbsolutePath(),
+ "-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();
+ JavaProcessLauncher underTest = new JavaProcessLauncherImpl(tempDir, commands, () -> processBuilder);
+ JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH);
+ command.setArgument("foo", "bar");
+ command.setArgument("baz", "woo");
+
+ 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();
+ JavaProcessLauncher.SystemProcessBuilder processBuilder = mock(JavaProcessLauncher.SystemProcessBuilder.class, RETURNS_MOCKS);
+ when(processBuilder.start()).thenThrow(new IOException("error"));
+ JavaProcessLauncher underTest = new JavaProcessLauncherImpl(tempDir, commands, () -> processBuilder);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Fail to launch process [es]");
+
+ underTest.launch(new JavaCommand(ProcessId.ELASTICSEARCH));
+ }
+
+ private static class TestProcessBuilder extends JavaProcessLauncher.SystemProcessBuilder {
+ 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/LifecycleTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/LifecycleTest.java
new file mode 100644
index 00000000000..79c9e47b273
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/LifecycleTest.java
@@ -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-process-monitor/src/test/java/org/sonar/application/process/ProcessMonitorImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessMonitorImplTest.java
new file mode 100644
index 00000000000..f1644b9c808
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessMonitorImplTest.java
@@ -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.application.process.ProcessMonitorImpl;
+import org.sonar.process.ProcessCommands;
+
+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 ProcessMonitorImplTest {
+
+ @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);
+
+ ProcessMonitorImpl underTest = new ProcessMonitorImpl(process, 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);
+
+ ProcessMonitorImpl underTest = new ProcessMonitorImpl(process, 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);
+
+ ProcessMonitorImpl underTest = new ProcessMonitorImpl(process, 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);
+
+ ProcessMonitorImpl underTest = new ProcessMonitorImpl(process, mock(ProcessCommands.class, Mockito.RETURNS_MOCKS));
+
+ // no failures
+ underTest.closeStreams();
+ }
+}
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
new file mode 100644
index 00000000000..84433436aa6
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/SQProcessTest.java
@@ -0,0 +1,327 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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 safeGuard = new DisableOnDebug(Timeout.seconds(10));
+
+ @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 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 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
new file mode 100644
index 00000000000..3fbe5f8a52c
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
@@ -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.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 safeGuard = new DisableOnDebug(Timeout.seconds(10));
+
+ 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/process/monitor/StreamGobblerTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StreamGobblerTest.java
index 643f0bdb8a7..2f1498d16dc 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/StreamGobblerTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StreamGobblerTest.java
@@ -17,13 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.process.monitor;
+package org.sonar.application.process;
+import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.slf4j.Logger;
-
-import java.io.InputStream;
+import org.sonar.application.process.StreamGobbler;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java
deleted file mode 100644
index 175d99c41d1..00000000000
--- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.process.monitor;
-
-import com.github.kevinsawicki.http.HttpRequest;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Supplier;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
-import org.assertj.core.api.AbstractAssert;
-import org.assertj.core.internal.Longs;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-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.process.Lifecycle.State;
-import org.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-import org.sonar.process.SystemExit;
-
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.monitor.Monitor.newMonitorBuilder;
-import static org.sonar.process.monitor.MonitorTest.HttpProcessClientAssert.assertThat;
-
-public class MonitorTest {
-
- private static File testJar;
-
- private FileSystem fileSystem = mock(FileSystem.class);
- private SystemExit exit = mock(SystemExit.class);
-
- private Monitor underTest;
-
- /**
- * Find the JAR file containing the test apps. Classes can't be moved in sonar-process-monitor because
- * they require sonar-process dependencies when executed here (sonar-process, commons-*, ...).
- */
- @BeforeClass
- public static void initTestJar() {
- File targetDir = new File("server/sonar-process/target");
- if (!targetDir.exists() || !targetDir.isDirectory()) {
- targetDir = new File("../sonar-process/target");
- }
- if (!targetDir.exists() || !targetDir.isDirectory()) {
- throw new IllegalStateException("target dir of sonar-process module not found. Please build it.");
- }
- Collection<File> jars = FileUtils.listFiles(targetDir, new String[] {"jar"}, false);
- for (File jar : jars) {
- if (jar.getName().startsWith("sonar-process-") && jar.getName().endsWith("-test-jar-with-dependencies.jar")) {
- testJar = jar;
- return;
- }
- }
- throw new IllegalStateException("No sonar-process-*-test-jar-with-dependencies.jar in " + targetDir);
- }
-
- /**
- * Safeguard
- */
- @Rule
- public TestRule globalTimeout = new DisableOnDebug(Timeout.seconds(60));
-
- /**
- * Temporary directory is used to interact with monitored processes, which write in it.
- */
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- private File tempDir;
-
- @Before
- public void setUp() throws Exception {
- tempDir = temp.newFolder();
-
- }
-
- /**
- * Safeguard
- */
- @After
- public void tearDown() {
- try {
- if (underTest != null) {
- underTest.stop();
- }
- } catch (Throwable ignored) {
- }
- }
-
- @Test
- public void fail_to_start_if_no_commands() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- try {
- underTest.start(Collections::emptyList);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("At least one command is required");
- }
- }
-
- @Test
- public void fail_to_start_multiple_times() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- underTest.start(() -> singletonList(newStandardProcessCommand()));
- boolean failed = false;
- try {
- underTest.start(() -> singletonList(newStandardProcessCommand()));
- } catch (IllegalStateException e) {
- failed = e.getMessage().equals("Can not start multiple times");
- }
- underTest.stop();
- assertThat(failed).isTrue();
- }
-
- @Test
- public void start_then_stop_gracefully() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient client = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- // blocks until started
- underTest.start(() -> singletonList(client.newCommand()));
-
- assertThat(client).isUp()
- .wasStartedBefore(System.currentTimeMillis());
-
- // blocks until stopped
- underTest.stop();
- assertThat(client)
- .isNotUp()
- .wasGracefullyTerminated();
- assertThat(underTest.getState()).isEqualTo(State.STOPPED);
- verify(fileSystem).reset();
- }
-
- @Test
- public void start_then_stop_sequence_of_commands() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
- underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
-
- // start p2 when p1 is fully started (ready)
- assertThat(p1)
- .isUp()
- .wasStartedBefore(p2);
- assertThat(p2)
- .isUp();
-
- underTest.stop();
-
- // stop in inverse order
- assertThat(p1)
- .isNotUp()
- .wasGracefullyTerminated();
- assertThat(p2)
- .isNotUp()
- .wasGracefullyTerminatedBefore(p1);
- verify(fileSystem).reset();
- }
-
- @Test
- public void stop_all_processes_if_monitor_shutdowns() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
- underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
- assertThat(p1).isUp();
- assertThat(p2).isUp();
-
- // emulate CTRL-C
- underTest.getShutdownHook().run();
- underTest.getShutdownHook().join();
-
- assertThat(p1).wasGracefullyTerminated();
- assertThat(p2).wasGracefullyTerminated();
-
- verify(fileSystem).reset();
- }
-
- @Test
- public void restart_all_processes_if_one_asks_for_restart() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
- underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
-
- assertThat(p1).isUp();
- assertThat(p2).isUp();
-
- p2.restart();
-
- assertThat(underTest.waitForOneRestart()).isTrue();
-
- assertThat(p1)
- .wasStarted(2)
- .wasGracefullyTerminated(1);
- assertThat(p2)
- .wasStarted(2)
- .wasGracefullyTerminated(1);
-
- underTest.stop();
-
- assertThat(p1)
- .wasStarted(2)
- .wasGracefullyTerminated(2);
- assertThat(p2)
- .wasStarted(2)
- .wasGracefullyTerminated(2);
-
- verify(fileSystem, times(2)).reset();
- }
-
- @Test
- public void restart_reloads_java_commands() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
-
- // a supplier that will return p1 the first time it's called and then p2 all the time
- Supplier<List<JavaCommand>> listSupplier = new Supplier<List<JavaCommand>>() {
- private int counter = 0;
- @Override
- public List<JavaCommand> get() {
- if (counter == 0) {
- counter++;
- return Collections.singletonList(p1.newCommand());
- } else {
- return Collections.singletonList(p2.newCommand());
- }
- }
- };
-
- underTest.start(listSupplier);
-
- assertThat(p1).isUp();
- assertThat(p2).isNotUp();
-
- p1.restart();
-
- assertThat(underTest.waitForOneRestart()).isTrue();
-
- assertThat(p1)
- .wasStarted(1)
- .wasGracefullyTerminated(1);
- assertThat(p2)
- .wasStarted(1)
- .isUp();
-
- underTest.stop();
- assertThat(p1)
- .wasStarted(1)
- .wasGracefullyTerminated(1);
- assertThat(p2)
- .wasStarted(1)
- .wasGracefullyTerminated(1);
- }
-
- @Test
- public void stop_all_processes_if_one_shutdowns() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
- underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
- assertThat(p1.isUp()).isTrue();
- assertThat(p2.isUp()).isTrue();
-
- // kill p1 -> waiting for detection by monitor than termination of p2
- p1.kill();
- underTest.awaitTermination();
-
- assertThat(p1)
- .isNotUp()
- .wasNotGracefullyTerminated();
- assertThat(p2)
- .isNotUp()
- .wasGracefullyTerminated();
-
- verify(fileSystem).reset();
- }
-
- @Test
- public void stop_all_processes_if_one_fails_to_start() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
- HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER, -1);
- try {
- underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
- fail();
- } catch (Exception expected) {
- assertThat(p1)
- .hasBeenReady()
- .wasGracefullyTerminated();
- assertThat(p2)
- .hasNotBeenReady()
- // self "gracefully terminated", even if startup went bad
- .wasGracefullyTerminated();
- }
- }
-
- @Test
- public void fail_to_start_if_bad_class_name() throws Exception {
- underTest = newDefaultMonitor(tempDir);
- JavaCommand command = new JavaCommand(ProcessId.ELASTICSEARCH)
- .addClasspath(testJar.getAbsolutePath())
- .setClassName("org.sonar.process.test.Unknown");
-
- try {
- underTest.start(() -> singletonList(command));
- fail();
- } catch (Exception e) {
- // expected
- // TODO improve, too many stacktraces logged
- }
- }
-
- @Test
- public void watchForHardStop_adds_a_hardStopWatcher_thread_and_starts_it() throws Exception {
- underTest = newDefaultMonitor(tempDir, true);
- assertThat(underTest.hardStopWatcher).isNull();
-
- HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.COMPUTE_ENGINE);
- underTest.start(() -> singletonList(p1.newCommand()));
-
- assertThat(underTest.hardStopWatcher).isNotNull();
- assertThat(underTest.hardStopWatcher.isAlive()).isTrue();
-
- p1.kill();
- underTest.awaitTermination();
-
- assertThat(underTest.hardStopWatcher.isAlive()).isFalse();
- }
-
- private Monitor newDefaultMonitor(File tempDir) throws IOException {
- return newDefaultMonitor(tempDir, false);
- }
-
- private Monitor newDefaultMonitor(File tempDir, boolean watchForHardStop) throws IOException {
- when(fileSystem.getTempDir()).thenReturn(tempDir);
- return newMonitorBuilder()
- .setProcessNumber(1)
- .setFileSystem(fileSystem)
- .setExit(exit)
- .setWatchForHardStop(watchForHardStop)
- .build();
- }
-
- /**
- * Interaction with {@link org.sonar.process.test.HttpProcess}
- */
- private class HttpProcessClient {
- private final int httpPort;
- private final ProcessId processId;
- private final File tempDir;
-
- private HttpProcessClient(File tempDir, ProcessId processId) throws IOException {
- this(tempDir, processId, NetworkUtils.freePort());
- }
-
- /**
- * Use httpPort=-1 to make server fail to start
- */
- private HttpProcessClient(File tempDir, ProcessId processId, int httpPort) throws IOException {
- this.tempDir = tempDir;
- this.processId = processId;
- this.httpPort = httpPort;
- }
-
- JavaCommand newCommand() {
- return new JavaCommand(processId)
- .addClasspath(testJar.getAbsolutePath())
- .setClassName("org.sonar.process.test.HttpProcess")
- .setArgument("httpPort", String.valueOf(httpPort));
- }
-
- /**
- * @see org.sonar.process.test.HttpProcess
- */
- boolean isUp() {
- try {
- HttpRequest httpRequest = HttpRequest.get("http://localhost:" + httpPort + "/" + "ping")
- .readTimeout(2000).connectTimeout(2000);
- return httpRequest.ok() && httpRequest.body().equals("ping");
- } catch (HttpRequest.HttpRequestException e) {
- return false;
- }
- }
-
- /**
- * @see org.sonar.process.test.HttpProcess
- */
- void kill() {
- try {
- HttpRequest.post("http://localhost:" + httpPort + "/" + "kill")
- .readTimeout(5000).connectTimeout(5000).ok();
- } catch (Exception e) {
- // HTTP request can't be fully processed, as web server hardly
- // calls "System.exit()"
- }
- }
-
- public void restart() {
- try {
- HttpRequest httpRequest = HttpRequest.post("http://localhost:" + httpPort + "/" + "restart")
- .readTimeout(5000).connectTimeout(5000);
- if (!httpRequest.ok() || !"ok".equals(httpRequest.body())) {
- throw new IllegalStateException("Wrong response calling restart");
- }
- } catch (Exception e) {
- throw new IllegalStateException("Failed to call restart", e);
- }
- }
-
- /**
- * @see org.sonar.process.test.HttpProcess
- */
- boolean wasGracefullyTerminated() {
- return fileExists("terminatedAt");
- }
-
- List<Long> wasStartingAt() {
- return readTimeFromFile("startingAt");
- }
-
- List<Long> wasGracefullyTerminatedAt() {
- return readTimeFromFile("terminatedAt");
- }
-
- boolean wasReady() {
- return fileExists("readyAt");
- }
-
- List<Long> wasReadyAt() {
- return readTimeFromFile("readyAt");
- }
-
- private List<Long> readTimeFromFile(String filename) {
- try {
- File file = new File(tempDir, httpPort + "-" + filename);
- if (file.isFile() && file.exists()) {
- String[] split = StringUtils.split(FileUtils.readFileToString(file), ',');
- List<Long> res = new ArrayList<>(split.length);
- for (String s : split) {
- res.add(Long.parseLong(s));
- }
- return res;
- }
- } catch (IOException e) {
- return Collections.emptyList();
- }
- throw new IllegalStateException("File does not exist");
- }
-
- private boolean fileExists(String filename) {
- File file = new File(tempDir, httpPort + "-" + filename);
- return file.isFile() && file.exists();
- }
- }
-
- public static class HttpProcessClientAssert extends AbstractAssert<HttpProcessClientAssert, HttpProcessClient> {
- Longs longs = Longs.instance();
-
- protected HttpProcessClientAssert(HttpProcessClient actual) {
- super(actual, HttpProcessClientAssert.class);
- }
-
- public static HttpProcessClientAssert assertThat(HttpProcessClient actual) {
- return new HttpProcessClientAssert(actual);
- }
-
- public HttpProcessClientAssert wasStarted(int times) {
- isNotNull();
-
- List<Long> startingAt = actual.wasStartingAt();
- longs.assertEqual(info, startingAt.size(), times);
-
- return this;
- }
-
- public HttpProcessClientAssert wasStartedBefore(long date) {
- isNotNull();
-
- List<Long> startingAt = actual.wasStartingAt();
- longs.assertEqual(info, startingAt.size(), 1);
- longs.assertLessThanOrEqualTo(info, startingAt.iterator().next(), date);
-
- return this;
- }
-
- public HttpProcessClientAssert wasStartedBefore(HttpProcessClient client) {
- isNotNull();
-
- List<Long> startingAt = actual.wasStartingAt();
- longs.assertEqual(info, startingAt.size(), 1);
- longs.assertLessThanOrEqualTo(info, startingAt.iterator().next(), client.wasStartingAt().iterator().next());
-
- return this;
- }
-
- public HttpProcessClientAssert wasTerminated(int times) {
- isNotNull();
-
- List<Long> terminatedAt = actual.wasGracefullyTerminatedAt();
- longs.assertEqual(info, terminatedAt.size(), 2);
-
- return this;
- }
-
- public HttpProcessClientAssert wasGracefullyTerminated() {
- isNotNull();
-
- if (!actual.wasGracefullyTerminated()) {
- failWithMessage("HttpClient %s should have been gracefully terminated", actual.processId.getKey());
- }
-
- return this;
- }
-
- public HttpProcessClientAssert wasNotGracefullyTerminated() {
- isNotNull();
-
- if (actual.wasGracefullyTerminated()) {
- failWithMessage("HttpClient %s should not have been gracefully terminated", actual.processId.getKey());
- }
-
- return this;
- }
-
- public HttpProcessClientAssert wasGracefullyTerminatedBefore(HttpProcessClient p1) {
- isNotNull();
-
- List<Long> wasGracefullyTerminatedAt = actual.wasGracefullyTerminatedAt();
- longs.assertEqual(info, wasGracefullyTerminatedAt.size(), 1);
- longs.assertLessThanOrEqualTo(info, wasGracefullyTerminatedAt.iterator().next(), p1.wasGracefullyTerminatedAt().iterator().next());
-
- return this;
- }
-
- public HttpProcessClientAssert wasGracefullyTerminated(int times) {
- isNotNull();
-
- List<Long> wasGracefullyTerminatedAt = actual.wasGracefullyTerminatedAt();
- longs.assertEqual(info, wasGracefullyTerminatedAt.size(), times);
-
- return this;
- }
-
- public HttpProcessClientAssert isUp() {
- isNotNull();
-
- // check condition
- if (!actual.isUp()) {
- failWithMessage("HttpClient %s should be up", actual.processId.getKey());
- }
-
- return this;
- }
-
- public HttpProcessClientAssert isNotUp() {
- isNotNull();
-
- if (actual.isUp()) {
- failWithMessage("HttpClient %s should not be up", actual.processId.getKey());
- }
-
- return this;
- }
-
- public HttpProcessClientAssert hasBeenReady() {
- isNotNull();
-
- // check condition
- if (!actual.wasReady()) {
- failWithMessage("HttpClient %s should been ready at least once", actual.processId.getKey());
- }
-
- return this;
- }
-
- public HttpProcessClientAssert hasNotBeenReady() {
- isNotNull();
-
- // check condition
- if (actual.wasReady()) {
- failWithMessage("HttpClient %s should never been ready", actual.processId.getKey());
- }
-
- return this;
- }
- }
-
- private JavaCommand newStandardProcessCommand() {
- return new JavaCommand(ProcessId.ELASTICSEARCH)
- .addClasspath(testJar.getAbsolutePath())
- .setClassName("org.sonar.process.test.StandardProcess");
- }
-
-}
diff --git a/sonar-application/src/test/resources/logback-test.xml b/server/sonar-process-monitor/src/test/resources/logback-test.xml
index 4c62d576dee..4c62d576dee 100644
--- a/sonar-application/src/test/resources/logback-test.xml
+++ b/server/sonar-process-monitor/src/test/resources/logback-test.xml
diff --git a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt b/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt
deleted file mode 100644
index 65b98c522da..00000000000
--- a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt
+++ /dev/null
@@ -1 +0,0 @@
-0PZz+G+f8mjr3sPn4+AhHg== \ No newline at end of file
diff --git a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt b/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt
deleted file mode 100644
index b33e179e5c8..00000000000
--- a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt
+++ /dev/null
@@ -1 +0,0 @@
-badbadbad== \ No newline at end of file
diff --git a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt b/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt
deleted file mode 100644
index ab83e4adc03..00000000000
--- a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 0PZz+G+f8mjr3sPn4+AhHg==
-
diff --git a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt b/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt
deleted file mode 100644
index 23f5ecf5104..00000000000
--- a/server/sonar-process-monitor/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt
+++ /dev/null
@@ -1 +0,0 @@
-IBxEUxZ41c8XTxyaah1Qlg== \ No newline at end of file
diff --git a/server/sonar-process-monitor/src/test/resources/org/sonar/process/PropsTest/sonar.properties b/server/sonar-process-monitor/src/test/resources/org/sonar/process/PropsTest/sonar.properties
deleted file mode 100644
index 5c06e58a32e..00000000000
--- a/server/sonar-process-monitor/src/test/resources/org/sonar/process/PropsTest/sonar.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-hello: world
-foo=bar
-java.io.tmpdir=/should/be/overridden
diff --git a/server/sonar-process-monitor/src/test/resources/sonar-dummy-app.jar b/server/sonar-process-monitor/src/test/resources/sonar-dummy-app.jar
deleted file mode 100644
index 6dfd458329a..00000000000
--- a/server/sonar-process-monitor/src/test/resources/sonar-dummy-app.jar
+++ /dev/null
Binary files differ
diff --git a/server/sonar-process/pom.xml b/server/sonar-process/pom.xml
index 020a3b3c717..d1b29121f91 100644
--- a/server/sonar-process/pom.xml
+++ b/server/sonar-process/pom.xml
@@ -86,36 +86,4 @@
<scope>test</scope>
</dependency>
</dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>test-jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <configuration>
- <descriptors>
- <descriptor>test-jar-with-dependencies.xml</descriptor>
- </descriptors>
- </configuration>
- <executions>
- <execution>
- <phase>package</phase>
- <goals>
- <goal>single</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
</project>
diff --git a/server/sonar-process/src/main/java/org/sonar/process/FileUtils.java b/server/sonar-process/src/main/java/org/sonar/process/FileUtils2.java
index 3f2d9da8245..fc77e9b74d9 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/FileUtils.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/FileUtils2.java
@@ -37,11 +37,11 @@ import static java.util.Objects.requireNonNull;
* This utility class provides Java NIO based replacement for some methods of
* {@link org.apache.commons.io.FileUtils Common IO FileUtils} class.
*/
-public final class FileUtils {
+public final class FileUtils2 {
private static final String DIRECTORY_CAN_NOT_BE_NULL = "Directory can not be null";
private static final EnumSet<FileVisitOption> FOLLOW_LINKS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
- private FileUtils() {
+ private FileUtils2() {
// prevents instantiation
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
index 25ca71f59fc..9d7060c680f 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
@@ -23,11 +23,8 @@ 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.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,13 +47,8 @@ public class Lifecycle {
private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
- private final List<LifecycleListener> listeners;
private State state = INIT;
- public Lifecycle(LifecycleListener... listeners) {
- this.listeners = Arrays.stream(listeners).filter(Objects::nonNull).collect(Collectors.toList());
- }
-
private static Map<State, Set<State>> buildTransitions() {
Map<State, Set<State>> res = new EnumMap<>(State.class);
res.put(INIT, toSet(STARTING));
@@ -90,7 +82,6 @@ public class Lifecycle {
if (TRANSITIONS.get(currentState).contains(to)) {
this.state = to;
res = true;
- listeners.forEach(listener -> listener.successfulTransition(currentState, to));
}
LOG.trace("tryToMoveTo from {} to {} => {}", currentState, to, res);
return res;
@@ -112,11 +103,4 @@ public class Lifecycle {
public int hashCode() {
return state.hashCode();
}
-
- public interface LifecycleListener {
- /**
- * Called when a transition from state {@code from} to state {@code to} was successful.
- */
- void successfulTransition(State from, State to);
- }
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java b/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java
index 54978819f03..f17e1aa3438 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java
@@ -25,7 +25,7 @@ import java.util.Map;
import org.apache.commons.lang.StringUtils;
import static java.lang.String.format;
-import static org.sonar.process.FileUtils.deleteQuietly;
+import static org.sonar.process.FileUtils2.deleteQuietly;
public class MinimumViableSystem {
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
index 28d7f0c4007..8af47f571ed 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
@@ -33,6 +33,12 @@ public class ProcessProperties {
public static final String CLUSTER_SEARCH_DISABLED = "sonar.cluster.search.disabled";
public static final String CLUSTER_SEARCH_HOSTS = "sonar.cluster.search.hosts";
public static final String CLUSTER_WEB_DISABLED = "sonar.cluster.web.disabled";
+ public static final String CLUSTER_MEMBERS = "sonar.cluster.members";
+ public static final String CLUSTER_PORT = "sonar.cluster.port";
+ public static final String CLUSTER_INTERFACES = "sonar.cluster.interfaces";
+ public static final String CLUSTER_NAME = "sonar.cluster.name";
+ public static final String HAZELCAST_LOG_LEVEL = "sonar.log.level.app.hazelcast";
+ public static final String CLUSTER_WEB_LEADER = "sonar.cluster.web.startupLeader";
public static final String JDBC_URL = "sonar.jdbc.url";
public static final String JDBC_DRIVER_PATH = "sonar.jdbc.driverPath";
@@ -42,6 +48,7 @@ public class ProcessProperties {
public static final String JDBC_MAX_WAIT = "sonar.jdbc.maxWait";
public static final String JDBC_MIN_EVICTABLE_IDLE_TIME_MILLIS = "sonar.jdbc.minEvictableIdleTimeMillis";
public static final String JDBC_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "sonar.jdbc.timeBetweenEvictionRunsMillis";
+ public static final String JDBC_EMBEDDED_PORT = "sonar.embeddedDatabase.port";
public static final String PATH_DATA = "sonar.path.data";
public static final String PATH_HOME = "sonar.path.home";
@@ -104,29 +111,45 @@ public class ProcessProperties {
public static Properties defaults() {
Properties defaults = new Properties();
- defaults.put(ProcessProperties.SEARCH_CLUSTER_NAME, "sonarqube");
- defaults.put(ProcessProperties.SEARCH_HOST, "127.0.0.1");
- defaults.put(ProcessProperties.SEARCH_JAVA_OPTS, "-Xmx1G -Xms256m -Xss256k -Djna.nosys=true " +
+ defaults.put(SEARCH_CLUSTER_NAME, "sonarqube");
+ defaults.put(SEARCH_HOST, "127.0.0.1");
+ defaults.put(SEARCH_JAVA_OPTS, "-Xmx1G -Xms256m -Xss256k -Djna.nosys=true " +
"-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly " +
"-XX:+HeapDumpOnOutOfMemoryError");
- defaults.put(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS, "");
-
- defaults.put(ProcessProperties.WEB_JAVA_OPTS, "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError");
- defaults.put(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS, "");
- defaults.put(ProcessProperties.CE_JAVA_OPTS, "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError");
- defaults.put(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS, "");
- defaults.put(ProcessProperties.JDBC_MAX_ACTIVE, "60");
- defaults.put(ProcessProperties.JDBC_MAX_IDLE, "5");
- defaults.put(ProcessProperties.JDBC_MIN_IDLE, "2");
- defaults.put(ProcessProperties.JDBC_MAX_WAIT, "5000");
- defaults.put(ProcessProperties.JDBC_MIN_EVICTABLE_IDLE_TIME_MILLIS, "600000");
- defaults.put(ProcessProperties.JDBC_TIME_BETWEEN_EVICTION_RUNS_MILLIS, "30000");
+ defaults.put(SEARCH_JAVA_ADDITIONAL_OPTS, "");
+
+ defaults.put(PATH_DATA, "data");
+ defaults.put(PATH_LOGS, "logs");
+ defaults.put(PATH_TEMP, "temp");
+ defaults.put(PATH_WEB, "web");
+
+ defaults.put(WEB_JAVA_OPTS, "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError");
+ defaults.put(WEB_JAVA_ADDITIONAL_OPTS, "");
+ defaults.put(CE_JAVA_OPTS, "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError");
+ defaults.put(CE_JAVA_ADDITIONAL_OPTS, "");
+ defaults.put(JDBC_MAX_ACTIVE, "60");
+ defaults.put(JDBC_MAX_IDLE, "5");
+ defaults.put(JDBC_MIN_IDLE, "2");
+ defaults.put(JDBC_MAX_WAIT, "5000");
+ defaults.put(JDBC_MIN_EVICTABLE_IDLE_TIME_MILLIS, "600000");
+ defaults.put(JDBC_TIME_BETWEEN_EVICTION_RUNS_MILLIS, "30000");
+
+ defaults.put(CLUSTER_ENABLED, "false");
+ defaults.put(CLUSTER_CE_DISABLED, "false");
+ defaults.put(CLUSTER_WEB_DISABLED, "false");
+ defaults.put(CLUSTER_SEARCH_DISABLED, "false");
+ defaults.put(CLUSTER_NAME, "");
+ defaults.put(CLUSTER_INTERFACES, "");
+ defaults.put(CLUSTER_MEMBERS, "");
+ defaults.put(CLUSTER_PORT, "9003");
+ defaults.put(HAZELCAST_LOG_LEVEL, "WARN");
+
return defaults;
}
private static Map<String, Integer> defaultPorts() {
Map<String, Integer> defaults = new HashMap<>();
- defaults.put(ProcessProperties.SEARCH_PORT, 9001);
+ defaults.put(SEARCH_PORT, 9001);
return defaults;
}
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java
index 6bf327a3525..a79a218615a 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java
@@ -19,9 +19,6 @@
*/
package org.sonar.process;
-import org.apache.commons.io.IOUtils;
-import org.slf4j.LoggerFactory;
-
import javax.annotation.Nullable;
public class ProcessUtils {
@@ -30,54 +27,6 @@ public class ProcessUtils {
// only static stuff
}
- /**
- * Do not abuse to this method. It uses exceptions to get status.
- * @return false if process is null or terminated, else true.
- */
- public static boolean isAlive(@Nullable Process process) {
- boolean alive = false;
- if (process != null) {
- try {
- process.exitValue();
- } catch (IllegalThreadStateException ignored) {
- alive = true;
- }
- }
- return alive;
- }
-
- /**
- * Send kill signal to stop process. Shutdown hooks are executed. It's the equivalent of SIGTERM on Linux.
- * Correctly tested on Java 6 and 7 on both Mac/MSWindows
- * @return true if the signal is sent, false if process is already down
- */
- public static boolean sendKillSignal(@Nullable Process process) {
- boolean sentSignal = false;
- if (isAlive(process)) {
- try {
- process.destroy();
- sentSignal = true;
- } catch (Exception e) {
- LoggerFactory.getLogger(ProcessUtils.class).error("Fail to kill " + process, e);
- }
- }
- return sentSignal;
- }
-
- public static void closeStreams(@Nullable Process process) {
- if (process!=null) {
- IOUtils.closeQuietly(process.getInputStream());
- IOUtils.closeQuietly(process.getOutputStream());
- IOUtils.closeQuietly(process.getErrorStream());
- }
- }
-
- public static void awaitTermination(Thread... threads) {
- for (Thread thread : threads) {
- awaitTermination(thread);
- }
- }
-
public static void awaitTermination(@Nullable Thread t) {
if (t == null || Thread.currentThread() == t) {
return;
@@ -88,6 +37,7 @@ public class ProcessUtils {
t.join();
} catch (InterruptedException e) {
// ignore, keep on waiting for t to stop
+ Thread.currentThread().interrupt();
}
}
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/FileUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/FileUtils2Test.java
index a8bd64a6d8e..b8f7268c346 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/FileUtilsTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/FileUtils2Test.java
@@ -34,7 +34,7 @@ import org.junit.rules.TemporaryFolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
-public class FileUtilsTest {
+public class FileUtils2Test {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
@@ -44,12 +44,12 @@ public class FileUtilsTest {
public void cleanDirectory_throws_NPE_if_file_is_null() throws IOException {
expectDirectoryCanNotBeNullNPE();
- FileUtils.cleanDirectory(null);
+ FileUtils2.cleanDirectory(null);
}
@Test
public void cleanDirectory_does_nothing_if_argument_does_not_exist() throws IOException {
- FileUtils.cleanDirectory(new File("/a/b/ToDoSSS"));
+ FileUtils2.cleanDirectory(new File("/a/b/ToDoSSS"));
}
@Test
@@ -59,7 +59,7 @@ public class FileUtilsTest {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("'" + file.getAbsolutePath() + "' is not a directory");
- FileUtils.cleanDirectory(file);
+ FileUtils2.cleanDirectory(file);
}
@Test
@@ -79,7 +79,7 @@ public class FileUtilsTest {
// on supporting FileSystem, target will change if directory is recreated
Object targetKey = getFileKey(target);
- FileUtils.cleanDirectory(target.toFile());
+ FileUtils2.cleanDirectory(target.toFile());
assertThat(target).isDirectory();
assertThat(childFile1).doesNotExist();
@@ -110,7 +110,7 @@ public class FileUtilsTest {
Object targetKey = getFileKey(target);
Object symLinkKey = getFileKey(symToDir);
- FileUtils.cleanDirectory(symToDir.toFile());
+ FileUtils2.cleanDirectory(symToDir.toFile());
assertThat(target).isDirectory();
assertThat(symToDir).isSymbolicLink();
@@ -124,7 +124,7 @@ public class FileUtilsTest {
@Test
public void deleteQuietly_does_not_fail_if_argument_is_null() {
- FileUtils.deleteQuietly(null);
+ FileUtils2.deleteQuietly(null);
}
@Test
@@ -132,7 +132,7 @@ public class FileUtilsTest {
File file = new File(temporaryFolder.newFolder(), "blablabl");
assertThat(file).doesNotExist();
- FileUtils.deleteQuietly(file);
+ FileUtils2.deleteQuietly(file);
}
@Test
@@ -149,7 +149,7 @@ public class FileUtilsTest {
assertThat(childFile2).isRegularFile();
assertThat(childDir2).isDirectory();
- FileUtils.deleteQuietly(target.toFile());
+ FileUtils2.deleteQuietly(target.toFile());
assertThat(target).doesNotExist();
assertThat(childFile1).doesNotExist();
@@ -168,7 +168,7 @@ public class FileUtilsTest {
assertThat(file1).isRegularFile();
assertThat(symLink).isSymbolicLink();
- FileUtils.deleteQuietly(symLink.toFile());
+ FileUtils2.deleteQuietly(symLink.toFile());
assertThat(symLink).doesNotExist();
assertThat(file1).isRegularFile();
@@ -178,14 +178,14 @@ public class FileUtilsTest {
public void deleteDirectory_throws_NPE_if_argument_is_null() throws IOException {
expectDirectoryCanNotBeNullNPE();
- FileUtils.deleteDirectory(null);
+ FileUtils2.deleteDirectory(null);
}
@Test
public void deleteDirectory_does_not_fail_if_file_does_not_exist() throws IOException {
File file = new File(temporaryFolder.newFolder(), "foo.d");
- FileUtils.deleteDirectory(file);
+ FileUtils2.deleteDirectory(file);
}
@Test
@@ -195,7 +195,7 @@ public class FileUtilsTest {
expectedException.expect(IOException.class);
expectedException.expectMessage("Directory '" + file.getAbsolutePath() + "' is a file");
- FileUtils.deleteDirectory(file);
+ FileUtils2.deleteDirectory(file);
}
@Test
@@ -211,7 +211,7 @@ public class FileUtilsTest {
expectedException.expect(IOException.class);
expectedException.expectMessage("Directory '" + symLink.toFile().getAbsolutePath() + "' is a symbolic link");
- FileUtils.deleteDirectory(symLink.toFile());
+ FileUtils2.deleteDirectory(symLink.toFile());
}
@Test
@@ -228,7 +228,7 @@ public class FileUtilsTest {
assertThat(childFile2).isRegularFile();
assertThat(childDir2).isDirectory();
- FileUtils.deleteQuietly(target.toFile());
+ FileUtils2.deleteQuietly(target.toFile());
assertThat(target).doesNotExist();
assertThat(childFile1).doesNotExist();
diff --git a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
index fc773dde56a..cbb75706ced 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
@@ -19,9 +19,6 @@
*/
package org.sonar.process;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.Objects;
import org.junit.Test;
@@ -54,18 +51,14 @@ public class LifecycleTest {
@Test
public void try_to_move_does_not_support_jumping_states() {
- TestLifeCycleListener listener = new TestLifeCycleListener();
- Lifecycle lifecycle = new Lifecycle(listener);
+ Lifecycle lifecycle = new Lifecycle();
assertThat(lifecycle.getState()).isEqualTo(INIT);
- assertThat(listener.getTransitions()).isEmpty();
assertThat(lifecycle.tryToMoveTo(STARTED)).isFalse();
assertThat(lifecycle.getState()).isEqualTo(INIT);
- assertThat(listener.getTransitions()).isEmpty();
assertThat(lifecycle.tryToMoveTo(STARTING)).isTrue();
assertThat(lifecycle.getState()).isEqualTo(STARTING);
- assertThat(listener.getTransitions()).containsOnly(new Transition(INIT, STARTING));
}
@Test
@@ -78,14 +71,11 @@ public class LifecycleTest {
@Test
public void can_move_to_STOPPING_from_STARTING_STARTED_OPERATIONAL_only() {
for (State state : values()) {
- TestLifeCycleListener listener = new TestLifeCycleListener();
- boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STOPPING);
- if (state == STARTING || state == STARTED || state == OPERATIONAL) {
+ boolean tryToMoveTo = newLifeCycle(state).tryToMoveTo(STOPPING);
+ if (state == STARTING || state == STARTED || state == OPERATIONAL) {
assertThat(tryToMoveTo).describedAs("from state " + state).isTrue();
- assertThat(listener.getTransitions()).containsOnly(new Transition(state, STOPPING));
} else {
assertThat(tryToMoveTo).describedAs("from state " + state).isFalse();
- assertThat(listener.getTransitions()).isEmpty();
}
}
}
@@ -93,73 +83,50 @@ public class LifecycleTest {
@Test
public void can_move_to_OPERATIONAL_from_STARTED_only() {
for (State state : values()) {
- TestLifeCycleListener listener = new TestLifeCycleListener();
- boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(OPERATIONAL);
+ boolean tryToMoveTo = newLifeCycle(state).tryToMoveTo(OPERATIONAL);
if (state == STARTED) {
assertThat(tryToMoveTo).describedAs("from state " + state).isTrue();
- assertThat(listener.getTransitions()).containsOnly(new Transition(state, OPERATIONAL));
} else {
assertThat(tryToMoveTo).describedAs("from state " + state).isFalse();
- assertThat(listener.getTransitions()).isEmpty();
}
}
}
@Test
public void can_move_to_STARTING_from_RESTARTING() {
- TestLifeCycleListener listener = new TestLifeCycleListener();
- assertThat(newLifeCycle(RESTARTING, listener).tryToMoveTo(STARTING)).isTrue();
- assertThat(listener.getTransitions()).containsOnly(new Transition(RESTARTING, STARTING));
+ assertThat(newLifeCycle(RESTARTING).tryToMoveTo(STARTING)).isTrue();
}
- private static Lifecycle newLifeCycle(State state, TestLifeCycleListener... listeners) {
+ private static Lifecycle newLifeCycle(State state) {
switch (state) {
case INIT:
- return new Lifecycle(listeners);
+ return new Lifecycle();
case STARTING:
- return newLifeCycle(INIT, state, listeners);
+ return newLifeCycle(INIT, state);
case STARTED:
- return newLifeCycle(STARTING, state, listeners);
+ return newLifeCycle(STARTING, state);
case OPERATIONAL:
- return newLifeCycle(STARTED, state, listeners);
+ return newLifeCycle(STARTED, state);
case RESTARTING:
- return newLifeCycle(OPERATIONAL, state, listeners);
+ return newLifeCycle(OPERATIONAL, state);
case STOPPING:
- return newLifeCycle(OPERATIONAL, state, listeners);
+ return newLifeCycle(OPERATIONAL, state);
case HARD_STOPPING:
- return newLifeCycle(STARTING, state, listeners);
+ return newLifeCycle(STARTING, state);
case STOPPED:
- return newLifeCycle(STOPPING, state, listeners);
+ return newLifeCycle(STOPPING, state);
default:
throw new IllegalArgumentException("Unsupported state " + state);
}
}
- private static Lifecycle newLifeCycle(State from, State to, TestLifeCycleListener... listeners) {
+ private static Lifecycle newLifeCycle(State from, State to) {
Lifecycle lifecycle;
- lifecycle = newLifeCycle(from, listeners);
+ lifecycle = newLifeCycle(from);
assertThat(lifecycle.tryToMoveTo(to)).isTrue();
- Arrays.stream(listeners).forEach(TestLifeCycleListener::clear);
return lifecycle;
}
- private static final class TestLifeCycleListener implements Lifecycle.LifecycleListener {
- private final List<Transition> transitions = new ArrayList<>();
-
- @Override
- public void successfulTransition(State from, State to) {
- transitions.add(new Transition(from, to));
- }
-
- public List<Transition> getTransitions() {
- return transitions;
- }
-
- public void clear() {
- this.transitions.clear();
- }
- }
-
private static final class Transition {
private final State from;
private final State to;
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java
index eff0d44763c..d1f872b904d 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java
@@ -30,7 +30,7 @@ import static org.sonar.process.ProcessUtils.awaitTermination;
public class ProcessUtilsTest {
@Rule
- public Timeout timeout= Timeout.seconds(5);
+ public Timeout timeout = Timeout.seconds(5);
@Test
public void private_constructor() {
@@ -39,7 +39,7 @@ public class ProcessUtilsTest {
@Test
public void awaitTermination_does_not_fail_on_null_Thread_argument() {
- awaitTermination((Thread) null);
+ awaitTermination(null);
}
@Test
@@ -50,22 +50,14 @@ public class ProcessUtilsTest {
@Test
public void awaitTermination_ignores_interrupted_exception_of_current_thread() throws InterruptedException {
final EverRunningThread runningThread = new EverRunningThread();
- final Thread safeJoiner = new Thread() {
- @Override
- public void run() {
- awaitTermination(runningThread);
+ final Thread safeJoiner = new Thread(() -> awaitTermination(runningThread));
+ final Thread simpleJoiner = new Thread(() -> {
+ try {
+ runningThread.join();
+ } catch (InterruptedException e) {
+ System.err.println("runningThread interruption detected in SimpleJoiner");
}
- };
- final Thread simpleJoiner = new Thread() {
- @Override
- public void run() {
- try {
- runningThread.join();
- } catch (InterruptedException e) {
- System.err.println("runningThread interruption detected in SimpleJoiner");
- }
- }
- };
+ });
runningThread.start();
safeJoiner.start();
simpleJoiner.start();
@@ -81,7 +73,7 @@ public class ProcessUtilsTest {
}
// safeJoiner must still be alive
- assertThat(safeJoiner.isAlive()).isTrue() ;
+ assertThat(safeJoiner.isAlive()).isTrue();
// stop runningThread
runningThread.stopIt();
@@ -94,11 +86,6 @@ public class ProcessUtilsTest {
safeJoiner.join();
}
- @Test
- public void awaitTermination_of_vararg_does_not_fail_when_there_is_a_null_or_current_thread() {
- awaitTermination(null, Thread.currentThread(), null);
- }
-
private static class EverRunningThread extends Thread {
private volatile boolean stop = false;
diff --git a/server/sonar-process/test-jar-with-dependencies.xml b/server/sonar-process/test-jar-with-dependencies.xml
deleted file mode 100644
index 832c66cc0be..00000000000
--- a/server/sonar-process/test-jar-with-dependencies.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
- <id>test-jar-with-dependencies</id>
- <formats>
- <format>jar</format>
- </formats>
- <includeBaseDirectory>false</includeBaseDirectory>
- <dependencySets>
- <dependencySet>
- <outputDirectory>/</outputDirectory>
- <useProjectArtifact>true</useProjectArtifact>
- <!-- we're creating the test-jar as an attachement -->
- <useProjectAttachments>true</useProjectAttachments>
- <unpack>true</unpack>
- <scope>test</scope>
- </dependencySet>
- </dependencySets>
-</assembly>
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/Cluster.java b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/Cluster.java
index 408ac4d61b1..5806e4d91f3 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/Cluster.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/Cluster.java
@@ -22,7 +22,7 @@ package org.sonar.server.platform.cluster;
public interface Cluster {
/**
- * Cluster is enabled when property {@link ClusterProperties#ENABLED} is {@code true}
+ * Cluster is enabled when property {@link org.sonar.process.ProcessProperties#CLUSTER_ENABLED} is {@code true}
*/
boolean isEnabled();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterImpl.java
index fec4a0a6657..d987b45f595 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterImpl.java
@@ -21,6 +21,9 @@ package org.sonar.server.platform.cluster;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.process.ProcessProperties;
+
+import static org.sonar.process.ProcessProperties.CLUSTER_WEB_LEADER;
public class ClusterImpl implements Cluster {
@@ -28,9 +31,9 @@ public class ClusterImpl implements Cluster {
private final boolean startupLeader;
public ClusterImpl(Settings settings) {
- this.enabled = settings.getBoolean(ClusterProperties.ENABLED);
+ this.enabled = settings.getBoolean(ProcessProperties.CLUSTER_ENABLED);
if (this.enabled) {
- this.startupLeader = settings.getBoolean(ClusterProperties.STARTUP_LEADER);
+ this.startupLeader = settings.getBoolean(CLUSTER_WEB_LEADER);
Loggers.get(ClusterImpl.class).info("Cluster enabled (startup {})", startupLeader ? "leader" : "follower");
} else {
this.startupLeader = true;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java
deleted file mode 100644
index f8f12111180..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.cluster;
-
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.sonar.api.PropertyType;
-import org.sonar.api.config.PropertyDefinition;
-
-public class ClusterProperties {
-
- public static final String ENABLED = "sonar.cluster.enabled";
- public static final String STARTUP_LEADER = "sonar.cluster.web.startupLeader";
-
- private ClusterProperties() {
- // only statics
- }
-
- public static List<PropertyDefinition> definitions() {
- return ImmutableList.of(
- PropertyDefinition.builder(ENABLED)
- .type(PropertyType.BOOLEAN)
- .defaultValue(String.valueOf(false))
- .hidden()
- .build(),
-
- PropertyDefinition.builder(STARTUP_LEADER)
- .type(PropertyType.BOOLEAN)
- .defaultValue(String.valueOf(false))
- .hidden()
- .build());
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
index 541df4fc57e..4f0042e6d2f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
@@ -47,7 +47,6 @@ import org.sonar.server.platform.ServerFileSystemImpl;
import org.sonar.server.platform.TempFolderProvider;
import org.sonar.server.platform.UrlSettings;
import org.sonar.server.platform.cluster.ClusterImpl;
-import org.sonar.server.platform.cluster.ClusterProperties;
import org.sonar.server.platform.db.EmbeddedDatabaseFactory;
import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
import org.sonar.server.rule.index.RuleIndex;
@@ -121,7 +120,6 @@ public class PlatformLevel1 extends PlatformLevel {
addAll(CorePropertyDefinitions.all());
// cluster
- addAll(ClusterProperties.definitions());
add(ClusterImpl.class);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java
index 08558c167aa..f867f414cea 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java
@@ -22,9 +22,8 @@ package org.sonar.server.platform.cluster;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.sonar.api.config.PropertyDefinitions;
-import org.sonar.api.config.Settings;
import org.sonar.api.config.MapSettings;
+import org.sonar.api.config.Settings;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,7 +32,7 @@ public class ClusterImplTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private Settings settings = new MapSettings(new PropertyDefinitions(ClusterProperties.definitions()));
+ private Settings settings = new MapSettings();
@Test
public void cluster_is_disabled_by_default() {
diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml
index 7e4fe8908ac..d863820c331 100644
--- a/sonar-application/pom.xml
+++ b/sonar-application/pom.xml
@@ -32,10 +32,6 @@
</dependency>
<dependency>
- <groupId>com.hazelcast</groupId>
- <artifactId>hazelcast</artifactId>
- </dependency>
- <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java
index c1a7e61d566..efc4a89d234 100644
--- a/sonar-application/src/main/java/org/sonar/application/App.java
+++ b/sonar-application/src/main/java/org/sonar/application/App.java
@@ -19,172 +19,76 @@
*/
package org.sonar.application;
-import com.google.common.annotations.VisibleForTesting;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import org.apache.commons.io.FilenameUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.Lifecycle;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.Stoppable;
-import org.sonar.process.monitor.JavaCommand;
-import org.sonar.process.monitor.Monitor;
-
-import static org.sonar.process.Lifecycle.State;
-import static org.sonar.process.ProcessId.APP;
-
-/**
- * Entry-point of process that starts and monitors ElasticSearch, the Web Server and the Compute Engine.
- */
-public class App implements Stoppable {
-
- private final Properties commandLineArguments;
- private final Function<Properties, Props> propsSupplier;
- private final JavaCommandFactory javaCommandFactory;
- private final Monitor monitor;
- private final Supplier<List<JavaCommand>> javaCommandSupplier;
- private final Cluster cluster;
-
- private App(Properties commandLineArguments) {
- this.commandLineArguments = commandLineArguments;
- this.propsSupplier = properties -> new PropsBuilder(properties, new JdbcSettings()).build();
- this.javaCommandFactory = new JavaCommandFactoryImpl();
- Props props = propsSupplier.apply(commandLineArguments);
-
- AppFileSystem appFileSystem = new AppFileSystem(props);
- appFileSystem.verifyProps();
- ClusterProperties clusterProperties = new ClusterProperties(props);
- clusterProperties.populateProps(props);
- AppLogging logging = new AppLogging();
- logging.configure(props);
- clusterProperties.validate();
- this.cluster = new Cluster(clusterProperties);
-
- // used by orchestrator
- boolean watchForHardStop = props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false);
- this.monitor = Monitor.newMonitorBuilder()
- .setProcessNumber(APP.getIpcIndex())
- .setFileSystem(appFileSystem)
- .setWatchForHardStop(watchForHardStop)
- .setWaitForOperational()
- .addListener(new AppLifecycleListener())
- .build();
- this.javaCommandSupplier = new ReloadableCommandSupplier(props, appFileSystem::ensureUnchangedConfiguration);
- }
-
- @VisibleForTesting
- App(Properties commandLineArguments, Function<Properties, Props> propsSupplier, Monitor monitor, CheckFSConfigOnReload checkFsConfigOnReload,
- JavaCommandFactory javaCommandFactory, Cluster cluster) {
- this.commandLineArguments = commandLineArguments;
- this.propsSupplier = propsSupplier;
- this.javaCommandFactory = javaCommandFactory;
- this.monitor = monitor;
- this.javaCommandSupplier = new ReloadableCommandSupplier(propsSupplier.apply(commandLineArguments), checkFsConfigOnReload);
- this.cluster = cluster;
- }
-
- public void start() throws InterruptedException {
- monitor.start(javaCommandSupplier);
- monitor.awaitTermination();
- }
-
- private static boolean isProcessEnabled(Props props, String disabledPropertyKey) {
- return !props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED) ||
- !props.valueAsBoolean(disabledPropertyKey);
- }
-
- static String starPath(File homeDir, String relativePath) {
- File dir = new File(homeDir, relativePath);
- return FilenameUtils.concat(dir.getAbsolutePath(), "*");
- }
-
- public static void main(String[] args) throws InterruptedException {
- CommandLineParser cli = new CommandLineParser();
- Properties rawProperties = cli.parseArguments(args);
-
- App app = new App(rawProperties);
- app.start();
- }
-
- @Override
- public void stopAsync() {
- if (cluster != null) {
- cluster.close();
- }
- if (monitor != null) {
- monitor.stop();
- }
- }
-
- private static class AppLifecycleListener implements Lifecycle.LifecycleListener {
- private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
-
- @Override
- public void successfulTransition(State from, State to) {
- if (to == State.OPERATIONAL) {
- LOGGER.info("SonarQube is up");
+import java.io.IOException;
+import org.sonar.application.config.AppSettings;
+import org.sonar.application.config.AppSettingsLoader;
+import org.sonar.application.config.AppSettingsLoaderImpl;
+import org.sonar.application.process.JavaCommandFactory;
+import org.sonar.application.process.JavaCommandFactoryImpl;
+import org.sonar.application.process.JavaProcessLauncher;
+import org.sonar.application.process.JavaProcessLauncherImpl;
+import org.sonar.application.process.StopRequestWatcher;
+import org.sonar.application.process.StopRequestWatcherImpl;
+import org.sonar.process.SystemExit;
+
+public class App {
+
+ private final SystemExit systemExit = new SystemExit();
+ private StopRequestWatcher stopRequestWatcher;
+
+ public void start(String[] cliArguments) throws IOException {
+ AppSettingsLoader settingsLoader = new AppSettingsLoaderImpl(cliArguments);
+ AppSettings settings = settingsLoader.load();
+ // order is important - logging must be configured before any other components (AppFileSystem, ...)
+ AppLogging logging = new AppLogging(settings);
+ logging.configure();
+ AppFileSystem fileSystem = new AppFileSystem(settings);
+
+ try (AppState appState = new AppStateFactory(settings).create()) {
+ AppReloader appReloader = new AppReloaderImpl(settingsLoader, fileSystem, appState, logging);
+ JavaCommandFactory javaCommandFactory = new JavaCommandFactoryImpl(settings);
+ fileSystem.reset();
+
+ try (JavaProcessLauncher javaProcessLauncher = new JavaProcessLauncherImpl(fileSystem.getTempDir())) {
+ Scheduler scheduler = new SchedulerImpl(settings, appReloader, javaCommandFactory, javaProcessLauncher, appState);
+
+ // intercepts CTRL-C
+ Runtime.getRuntime().addShutdownHook(new ShutdownHook(scheduler));
+
+ scheduler.schedule();
+
+ stopRequestWatcher = StopRequestWatcherImpl.create(settings, scheduler, fileSystem);
+ stopRequestWatcher.startWatching();
+
+ scheduler.awaitTermination();
+ stopRequestWatcher.stopWatching();
}
+ systemExit.exit(0);
}
}
- @FunctionalInterface
- interface CheckFSConfigOnReload extends Consumer<Props> {
-
+ public static void main(String... args) throws IOException {
+ new App().start(args);
}
- private class ReloadableCommandSupplier implements Supplier<List<JavaCommand>> {
- private final Props initialProps;
- private final CheckFSConfigOnReload checkFsConfigOnReload;
- private boolean initialPropsConsumed = false;
+ private class ShutdownHook extends Thread {
+ private final Scheduler scheduler;
- ReloadableCommandSupplier(Props initialProps, CheckFSConfigOnReload checkFsConfigOnReload) {
- this.initialProps = initialProps;
- this.checkFsConfigOnReload = checkFsConfigOnReload;
+ public ShutdownHook(Scheduler scheduler) {
+ super("SonarQube Shutdown Hook");
+ this.scheduler = scheduler;
}
@Override
- public List<JavaCommand> get() {
- if (!initialPropsConsumed) {
- initialPropsConsumed = true;
- return createCommands(this.initialProps);
- }
- return recreateCommands();
- }
-
- private List<JavaCommand> recreateCommands() {
- Props reloadedProps = propsSupplier.apply(commandLineArguments);
- AppFileSystem appFileSystem = new AppFileSystem(reloadedProps);
- appFileSystem.verifyProps();
- checkFsConfigOnReload.accept(reloadedProps);
- AppLogging logging = new AppLogging();
- logging.configure(reloadedProps);
-
- return createCommands(reloadedProps);
- }
-
- private List<JavaCommand> createCommands(Props props) {
- File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
- List<JavaCommand> commands = new ArrayList<>(3);
- if (isProcessEnabled(props, ProcessProperties.CLUSTER_SEARCH_DISABLED)) {
- commands.add(javaCommandFactory.createESCommand(props, homeDir));
- }
-
- if (isProcessEnabled(props, ProcessProperties.CLUSTER_WEB_DISABLED)) {
- commands.add(javaCommandFactory.createWebCommand(props, homeDir));
- }
+ public void run() {
+ systemExit.setInShutdownHook();
- if (isProcessEnabled(props, ProcessProperties.CLUSTER_CE_DISABLED)) {
- commands.add(javaCommandFactory.createCeCommand(props, homeDir));
+ if (stopRequestWatcher != null) {
+ stopRequestWatcher.stopWatching();
}
- return commands;
+ // blocks until everything is corrected terminated
+ scheduler.terminate();
}
}
}
diff --git a/sonar-application/src/main/java/org/sonar/application/Cluster.java b/sonar-application/src/main/java/org/sonar/application/Cluster.java
deleted file mode 100644
index 621af696320..00000000000
--- a/sonar-application/src/main/java/org/sonar/application/Cluster.java
+++ /dev/null
@@ -1,103 +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 com.google.common.annotations.VisibleForTesting;
-import com.hazelcast.cluster.ClusterState;
-import com.hazelcast.config.Config;
-import com.hazelcast.config.JoinConfig;
-import com.hazelcast.config.NetworkConfig;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
-import javax.annotation.Nonnull;
-
-/**
- * Manager for the cluster communication between Main Processes
- */
-public class Cluster implements AutoCloseable {
- /**
- * The Hazelcast instance.
- */
- @VisibleForTesting
- final HazelcastInstance hazelcastInstance;
-
- /**
- * Instantiates a new Cluster.
- *
- * @param clusterProperties The properties of the cluster read from configuration
- */
- protected Cluster(@Nonnull ClusterProperties clusterProperties) {
- if (clusterProperties.isEnabled()) {
- Config hzConfig = new Config();
- // Configure the network instance
- NetworkConfig netConfig = hzConfig.getNetworkConfig();
- netConfig.setPort(clusterProperties.getPort())
- .setPortAutoIncrement(clusterProperties.isPortAutoincrement());
-
- if (!clusterProperties.getInterfaces().isEmpty()) {
- netConfig.getInterfaces()
- .setEnabled(true)
- .setInterfaces(clusterProperties.getInterfaces());
- }
-
- // 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.getMembers());
-
- // 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");
-
- // We are not using the partition group of Hazelcast, so disabling it
- hzConfig.getPartitionGroupConfig().setEnabled(false);
-
- hazelcastInstance = Hazelcast.newHazelcastInstance(hzConfig);
- } else {
- hazelcastInstance = null;
- }
- }
-
- /**
- * Is the cluster active
- *
- * @return the boolean
- */
- public boolean isActive() {
- return hazelcastInstance != null && hazelcastInstance.getCluster().getClusterState() == ClusterState.ACTIVE;
- }
-
- @Override
- public void close() {
- if (hazelcastInstance != null) {
- hazelcastInstance.shutdown();
- }
- }
-}
diff --git a/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java b/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java
deleted file mode 100644
index c0e15e0ca1b..00000000000
--- a/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java
+++ /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;
-
-import javax.annotation.Nonnull;
-import org.apache.commons.lang.StringUtils;
-
-enum ClusterParameters {
- ENABLED("sonar.cluster.enabled", Boolean.FALSE.toString()),
- MEMBERS("sonar.cluster.members", ""),
- PORT("sonar.cluster.port", Integer.toString(9003)),
- PORT_AUTOINCREMENT("sonar.cluster.port_autoincrement", Boolean.FALSE.toString()),
- INTERFACES("sonar.cluster.interfaces", ""),
- NAME("sonar.cluster.name", ""),
- HAZELCAST_LOG_LEVEL("sonar.log.level.app.hazelcast", "WARN");
-
- private final String name;
- private final String defaultValue;
-
- ClusterParameters(@Nonnull String name, @Nonnull String defaultValue) {
- this.name = name;
- this.defaultValue = defaultValue;
- }
-
- String getName() {
- return name;
- }
-
- String getDefaultValue() {
- return defaultValue;
- }
-
- boolean getDefaultValueAsBoolean() {
- return "true".equalsIgnoreCase(defaultValue);
- }
-
- Integer getDefaultValueAsInt() {
- if (StringUtils.isNotEmpty(defaultValue)) {
- try {
- return Integer.parseInt(defaultValue);
- } catch (NumberFormatException e) {
- throw new IllegalStateException("Default value of property " + name + " is not an integer: " + defaultValue, e);
- }
- }
- return null;
- }
-}
diff --git a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java
deleted file mode 100644
index 8058beb1018..00000000000
--- a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java
+++ /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;
-
-import java.io.File;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.monitor.JavaCommand;
-
-import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST;
-import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT;
-import static org.sonar.process.ProcessProperties.HTTP_PROXY_HOST;
-import static org.sonar.process.ProcessProperties.HTTP_PROXY_PORT;
-
-public class JavaCommandFactoryImpl implements JavaCommandFactory {
- /**
- * Properties about proxy that must be set as system properties
- */
- private static final String[] PROXY_PROPERTY_KEYS = new String[] {
- HTTP_PROXY_HOST,
- HTTP_PROXY_PORT,
- "http.nonProxyHosts",
- HTTPS_PROXY_HOST,
- HTTPS_PROXY_PORT,
- "http.auth.ntlm.domain",
- "socksProxyHost",
- "socksProxyPort"};
-
- @Override
- public JavaCommand createESCommand(Props props, File workDir) {
- return newJavaCommand(ProcessId.ELASTICSEARCH, props, workDir)
- .addJavaOptions("-Djava.awt.headless=true")
- .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
- .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
- .setClassName("org.sonar.search.SearchServer")
- .addClasspath("./lib/common/*")
- .addClasspath("./lib/search/*");
- }
-
- @Override
- public JavaCommand createWebCommand(Props props, File workDir) {
- JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, props, workDir)
- .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS)
- .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS))
- .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS))
- // required for logback tomcat valve
- .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS))
- .setClassName("org.sonar.server.app.WebServer")
- .addClasspath("./lib/common/*")
- .addClasspath("./lib/server/*");
- String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
- if (driverPath != null) {
- command.addClasspath(driverPath);
- }
- return command;
- }
-
- @Override
- public JavaCommand createCeCommand(Props props, File workDir) {
- JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, props, workDir)
- .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS)
- .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_OPTS))
- .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS))
- .setClassName("org.sonar.ce.app.CeServer")
- .addClasspath("./lib/common/*")
- .addClasspath("./lib/server/*")
- .addClasspath("./lib/ce/*");
- String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
- if (driverPath != null) {
- command.addClasspath(driverPath);
- }
- return command;
- }
-
- private static JavaCommand newJavaCommand(ProcessId id, Props props, File workDir) {
- JavaCommand command = new JavaCommand(id)
- .setWorkDir(workDir)
- .setArguments(props.rawProperties());
-
- for (String key : PROXY_PROPERTY_KEYS) {
- if (props.contains(key)) {
- command.addJavaOption("-D" + key + "=" + props.value(key));
- }
- }
- // defaults of HTTPS are the same than HTTP defaults
- setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
- setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
- return command;
- }
-
- private static void setSystemPropertyToDefaultIfNotSet(JavaCommand command, Props props, String httpsProperty, String httpProperty) {
- if (!props.contains(httpsProperty) && props.contains(httpProperty)) {
- command.addJavaOption("-D" + httpsProperty + "=" + props.value(httpProperty));
- }
- }
-}
diff --git a/sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java b/sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java
deleted file mode 100644
index 2f280758f62..00000000000
--- a/sonar-application/src/test/java/org/sonar/application/AppFileSystemTest.java
+++ /dev/null
@@ -1,263 +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 java.util.Properties;
-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.process.AllProcessesCommands;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
-
-public class AppFileSystemTest {
-
- private static final String PROPERTY_SONAR_PATH_WEB = "sonar.path.web";
- private static final String PROPERTY_SONAR_PATH_DATA = "sonar.path.data";
- private static final String PROPERTY_SONAR_PATH_LOGS = "sonar.path.logs";
- private static final String PROPERTY_SONAR_PATH_TEMP = "sonar.path.temp";
- private static final String NON_DEFAULT_DATA_DIR_NAME = "toto";
- private static final String NON_DEFAULT_WEB_DIR_NAME = "tutu";
- private static final String NON_DEFAULT_LOGS_DIR_NAME = "titi";
- private static final String NON_DEFAULT_TEMP_DIR_NAME = "tatta";
- private static final String DEFAULT_DATA_DIR_NAME = "data";
- private static final String DEFAULT_WEB_DIR_NAME = "web";
- private static final String DEFAULT_LOGS_DIR_NAME = "logs";
- private static final String DEFAULT_TEMP_DIR_NAME = "temp";
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private File homeDir;
- private Properties properties;
-
- @Before
- public void before() throws IOException {
- homeDir = temp.newFolder();
-
- properties = new Properties();
- properties.setProperty("sonar.path.home", homeDir.getAbsolutePath());
- }
-
- @Test
- public void verifyProps_set_dir_path_absolute_based_on_home_dir_and_default_names_when_no_property() {
- Props props = new Props(properties);
- AppFileSystem underTest = new AppFileSystem(props);
-
- underTest.verifyProps();
-
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_DATA)).isEqualTo(new File(homeDir, DEFAULT_DATA_DIR_NAME).getAbsolutePath());
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_WEB)).isEqualTo(new File(homeDir, DEFAULT_WEB_DIR_NAME).getAbsolutePath());
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_LOGS)).isEqualTo(new File(homeDir, DEFAULT_LOGS_DIR_NAME).getAbsolutePath());
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_TEMP)).isEqualTo(new File(homeDir, DEFAULT_TEMP_DIR_NAME).getAbsolutePath());
- }
-
- @Test
- public void verifyProps_can_be_called_multiple_times() {
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
-
- underTest.verifyProps();
- underTest.verifyProps();
- }
-
- @Test
- public void reset_throws_ISE_if_verifyProps_not_called_first() throws Exception {
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("method verifyProps must be called first");
-
- underTest.reset();
- }
-
- @Test
- public void verifyProps_makes_dir_path_absolute_based_on_home_dir_when_relative() throws Exception {
- properties.setProperty(PROPERTY_SONAR_PATH_WEB, NON_DEFAULT_WEB_DIR_NAME);
- properties.setProperty(PROPERTY_SONAR_PATH_DATA, NON_DEFAULT_DATA_DIR_NAME);
- properties.setProperty(PROPERTY_SONAR_PATH_LOGS, NON_DEFAULT_LOGS_DIR_NAME);
- properties.setProperty(PROPERTY_SONAR_PATH_TEMP, NON_DEFAULT_TEMP_DIR_NAME);
-
- Props props = new Props(properties);
- AppFileSystem underTest = new AppFileSystem(props);
-
- underTest.verifyProps();
-
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_DATA)).isEqualTo(new File(homeDir, NON_DEFAULT_DATA_DIR_NAME).getAbsolutePath());
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_WEB)).isEqualTo(new File(homeDir, NON_DEFAULT_WEB_DIR_NAME).getAbsolutePath());
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_LOGS)).isEqualTo(new File(homeDir, NON_DEFAULT_LOGS_DIR_NAME).getAbsolutePath());
- assertThat(props.nonNullValue(PROPERTY_SONAR_PATH_TEMP)).isEqualTo(new File(homeDir, NON_DEFAULT_TEMP_DIR_NAME).getAbsolutePath());
- }
-
- @Test
- public void reset_creates_dir_all_dirs_if_they_don_t_exist() throws Exception {
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
-
- underTest.verifyProps();
-
- File dataDir = new File(homeDir, DEFAULT_DATA_DIR_NAME);
- File webDir = new File(homeDir, DEFAULT_WEB_DIR_NAME);
- File logsDir = new File(homeDir, DEFAULT_LOGS_DIR_NAME);
- File tempDir = new File(homeDir, DEFAULT_TEMP_DIR_NAME);
- assertThat(dataDir).doesNotExist();
- assertThat(webDir).doesNotExist();
- assertThat(logsDir).doesNotExist();
- assertThat(tempDir).doesNotExist();
-
- underTest.reset();
-
- assertThat(dataDir).exists().isDirectory();
- assertThat(webDir).exists().isDirectory();
- assertThat(logsDir).exists().isDirectory();
- assertThat(tempDir).exists().isDirectory();
- }
-
- @Test
- public void reset_deletes_content_of_temp_dir_but_not_temp_dir_itself_if_it_already_exists() throws Exception {
- File tempDir = new File(homeDir, DEFAULT_TEMP_DIR_NAME);
- assertThat(tempDir.mkdir()).isTrue();
- Object tempDirKey = getFileKey(tempDir);
- File fileInTempDir = new File(tempDir, "someFile.txt");
- assertThat(fileInTempDir.createNewFile()).isTrue();
-
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
- underTest.verifyProps();
- underTest.reset();
-
- assertThat(tempDir).exists();
- assertThat(fileInTempDir).doesNotExist();
- assertThat(getFileKey(tempDir)).isEqualTo(tempDirKey);
- }
-
- @Test
- public void reset_deletes_content_of_temp_dir_but_not_sharedmemory_file() throws Exception {
- File tempDir = new File(homeDir, DEFAULT_TEMP_DIR_NAME);
- 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();
-
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
- underTest.verifyProps();
- 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 {
- File tempDir = new File(homeDir, DEFAULT_TEMP_DIR_NAME);
- assertThat(tempDir.mkdir()).isTrue();
- try (AllProcessesCommands commands = new AllProcessesCommands(tempDir)) {
- for (int i = 0; i < MAX_PROCESSES; i++) {
- commands.create(i).setUp();
- }
-
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
- underTest.verifyProps();
- 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(PROPERTY_SONAR_PATH_DATA);
- }
-
- @Test
- public void reset_throws_ISE_if_web_dir_is_a_file() throws Exception {
- resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_WEB);
- }
-
- @Test
- public void reset_throws_ISE_if_logs_dir_is_a_file() throws Exception {
- resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_LOGS);
- }
-
- @Test
- public void reset_throws_ISE_if_temp_dir_is_a_file() throws Exception {
- resetThrowsISEIfDirIsAFile(PROPERTY_SONAR_PATH_TEMP);
- }
-
- private void resetThrowsISEIfDirIsAFile(String property) throws IOException {
- File file = new File(homeDir, "zoom.store");
- assertThat(file.createNewFile()).isTrue();
-
- properties.setProperty(property, "zoom.store");
-
- AppFileSystem underTest = new AppFileSystem(new Props(properties));
-
- underTest.verifyProps();
-
- 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);
-// try {
-// FileUtils.touch(dataDir);
-// new PropsBuilder(new Properties(), jdbcSettings, homeDir).build();
-// fail();
-// } catch (IllegalStateException e) {
-// assertThat(e.getMessage()).startsWith("Property 'sonar.path.data' is not valid, not a directory: " + dataDir.getAbsolutePath());
-// }
-// }
-
-}
diff --git a/sonar-application/src/test/java/org/sonar/application/AppTest.java b/sonar-application/src/test/java/org/sonar/application/AppTest.java
deleted file mode 100644
index 3ae188cc7b3..00000000000
--- a/sonar-application/src/test/java/org/sonar/application/AppTest.java
+++ /dev/null
@@ -1,232 +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.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Properties;
-import java.util.function.Supplier;
-import org.apache.commons.io.FilenameUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentCaptor;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.process.monitor.JavaCommand;
-import org.sonar.process.monitor.Monitor;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-public class AppTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private final JavaCommand esCommand = mock(JavaCommand.class);
- private final JavaCommand webCommand = mock(JavaCommand.class);
- private final JavaCommand ceCommand = mock(JavaCommand.class);
- private final JavaCommandFactory javaCommandFactory = new JavaCommandFactory() {
-
- @Override
- public JavaCommand createESCommand(Props props, File homeDir) {
- return AppTest.this.esCommand;
- }
-
- @Override
- public JavaCommand createWebCommand(Props props, File homeDir) {
- return AppTest.this.webCommand;
- }
-
- @Override
- public JavaCommand createCeCommand(Props props, File homeDir) {
- return AppTest.this.ceCommand;
- }
- };
- private App.CheckFSConfigOnReload checkFsConfigOnReload = mock(App.CheckFSConfigOnReload.class);
-
- @Test
- public void starPath() throws IOException {
- File homeDir = temp.newFolder();
- String startPath = App.starPath(homeDir, "lib/search");
- assertThat(FilenameUtils.normalize(startPath, true))
- .endsWith("*")
- .startsWith(FilenameUtils.normalize(homeDir.getAbsolutePath(), true));
- }
-
- @Test
- public void start_all_processes_if_cluster_mode_is_disabled() throws Exception {
- Props props = initDefaultProps();
- Monitor monitor = mock(Monitor.class);
- Cluster cluster = mock(Cluster.class);
- App app = new App(props.rawProperties(), properties -> props, monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
- app.start();
-
- ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
- verify(monitor).start(argument.capture());
- assertThat(argument.getValue().get())
- .containsExactly(esCommand, webCommand, ceCommand);
-
- app.stopAsync();
- verify(monitor).stop();
- }
-
- @Test
- public void start_only_web_server_node_in_cluster() throws Exception {
- Props props = initDefaultProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
- props.set(ProcessProperties.CLUSTER_CE_DISABLED, "true");
- props.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
-
- List<JavaCommand> commands = start(props);
-
- assertThat(commands).containsOnly(webCommand);
- }
-
- @Test
- public void start_only_compute_engine_node_in_cluster() throws Exception {
- Props props = initDefaultProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
- props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
- props.set(ProcessProperties.CLUSTER_SEARCH_DISABLED, "true");
-
- List<JavaCommand> commands = start(props);
-
- assertThat(commands).contains(ceCommand);
- }
-
- @Test
- public void start_only_elasticsearch_node_in_cluster() throws Exception {
- Props props = initDefaultProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
- props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
- props.set(ProcessProperties.CLUSTER_CE_DISABLED, "true");
-
- List<JavaCommand> commands = start(props);
-
- assertThat(commands).containsOnly(esCommand);
- }
-
- @Test
- public void javaCommands_supplier_reloads_properties_and_ensure_filesystem_props_have_not_changed() throws Exception {
- // initial props is not cluster => all three processes must be created
- Props initialProps = initDefaultProps();
- // second props is cluster with only ES and CE enabled => only two processes must be created
- Props props = initDefaultProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
- props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
- // setup an App that emulate reloading of props returning different configuration
- Iterator<Props> propsIterator = Arrays.asList(initialProps, props).iterator();
- Monitor monitor = mock(Monitor.class);
- Cluster cluster = mock(Cluster.class);
- App app = new App(initialProps.rawProperties(), properties -> propsIterator.next(), monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
- // start App and capture the JavaCommand supplier it provides to the Monitor
- app.start();
- Supplier<List<JavaCommand>> supplier = captureJavaCommandsSupplier(monitor);
-
- // first call, consumes initial props
- assertThat(supplier.get()).containsExactly(esCommand, webCommand, ceCommand);
- verifyZeroInteractions(checkFsConfigOnReload);
-
- // second call, consumes second props
- assertThat(supplier.get()).containsExactly(esCommand, ceCommand);
- verify(checkFsConfigOnReload).accept(props);
-
- // third call will trigger error from iterator
- expectedException.expect(NoSuchElementException.class);
- supplier.get();
- }
-
- @Test
- public void javaCommands_supplier_propagate_errors_from_CheckFsConfigOnReload_instance() throws Exception {
- // initial props is not cluster => all three processes must be created
- Props initialProps = initDefaultProps();
- // second props is cluster with only ES and CE enabled => only two processes must be created
- Props props = initDefaultProps();
- props.set(ProcessProperties.CLUSTER_ENABLED, "true");
- props.set(ProcessProperties.CLUSTER_WEB_DISABLED, "true");
- // setup an App that emulate reloading of props returning different configuration
- Iterator<Props> propsIterator = Arrays.asList(initialProps, props).iterator();
- Monitor monitor = mock(Monitor.class);
- Cluster cluster = mock(Cluster.class);
- App app = new App(initialProps.rawProperties(), properties -> propsIterator.next(), monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
- // start App and capture the JavaCommand supplier it provides to the Monitor
- app.start();
- Supplier<List<JavaCommand>> supplier = captureJavaCommandsSupplier(monitor);
-
- // first call, consumes initial props
- assertThat(supplier.get()).containsExactly(esCommand, webCommand, ceCommand);
- verifyZeroInteractions(checkFsConfigOnReload);
-
- // second call, consumes second props and propagates exception thrown by checkFsConfigOnReload
- IllegalStateException expected = new IllegalStateException("emulating change of FS properties is not supported exception");
- doThrow(expected).when(checkFsConfigOnReload).accept(props);
- try {
- supplier.get();
- fail("Supplier should have propagated exception from checkFsConfigOnReload");
- } catch (IllegalStateException e) {
- assertThat(e).isSameAs(expected);
- }
- }
-
- private Supplier<List<JavaCommand>> captureJavaCommandsSupplier(Monitor monitor) throws InterruptedException {
- ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
- verify(monitor).start(argument.capture());
-
- return argument.getValue();
- }
-
- private Props initDefaultProps() throws IOException {
- Props props = new Props(new Properties());
- ProcessProperties.completeDefaults(props);
- props.set(ProcessProperties.PATH_HOME, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
- props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
- return props;
- }
-
- private List<JavaCommand> start(Props props) throws Exception {
- Monitor monitor = mock(Monitor.class);
- Cluster cluster = mock(Cluster.class);
- App app = new App(props.rawProperties(), properties -> props, monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
- app.start();
- ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
- verify(monitor).start(argument.capture());
- return argument.getValue().get();
- }
-
- private ArgumentCaptor<Supplier<List<JavaCommand>>> newJavaCommandArgumentCaptor() {
- Class<Supplier<List<JavaCommand>>> listClass = (Class<Supplier<List<JavaCommand>>>) (Class) List.class;
- return ArgumentCaptor.forClass(listClass);
- }
-
-}
diff --git a/sonar-application/src/test/java/org/sonar/application/ClusterPropertiesTest.java b/sonar-application/src/test/java/org/sonar/application/ClusterPropertiesTest.java
deleted file mode 100644
index 4114a491970..00000000000
--- a/sonar-application/src/test/java/org/sonar/application/ClusterPropertiesTest.java
+++ /dev/null
@@ -1,181 +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.List;
-import java.util.Properties;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-
-public class ClusterPropertiesTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void test_default_values() throws Exception {
- ClusterProperties props = new ClusterProperties(new Props(new Properties()));
-
- assertThat(props.getInterfaces())
- .isEqualTo(Collections.emptyList());
- assertThat(props.isPortAutoincrement())
- .isEqualTo(false);
- assertThat(props.getPort())
- .isEqualTo(9003);
- assertThat(props.isEnabled())
- .isEqualTo(false);
- assertThat(props.getMembers())
- .isEqualTo(Collections.emptyList());
- assertThat(props.getName())
- .isEqualTo("");
- assertThat(props.getLogLevel())
- .isEqualTo("WARN");
- }
-
-
- @Test
- public void test_port_parameter() {
- Props props = new Props(new Properties());
- props.set(ClusterParameters.ENABLED.getName(), "true");
- props.set(ClusterParameters.NAME.getName(), "sonarqube");
-
- Stream.of("-50", "0", "65536", "128563").forEach(
- port -> {
- props.set(ClusterParameters.PORT.getName(), port);
-
- ClusterProperties clusterProperties = new ClusterProperties(props);
- 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() {
- Props props = new Props(new Properties());
- props.set(ClusterParameters.ENABLED.getName(), "true");
- props.set(ClusterParameters.NAME.getName(), "sonarqube");
- props.set(ClusterParameters.INTERFACES.getName(), "8.8.8.8"); // This IP belongs to Google
-
- ClusterProperties clusterProperties = new ClusterProperties(props);
- 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 test_missing_name() {
- Props props = new Props(new Properties());
- props.set(ClusterParameters.ENABLED.getName(), "true");
- props.set(ClusterParameters.NAME.getName(), "");
- ClusterProperties clusterProperties = new ClusterProperties(props);
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage(
- String.format("Cluster have been enabled but a %s has not been defined.",
- ClusterParameters.NAME.getName())
- );
- clusterProperties.validate();
- }
-
- @Test
- public void validate_does_not_fail_if_cluster_enabled_and_name_specified() {
- Props props = new Props(new Properties());
- props.set(ClusterParameters.ENABLED.getName(), "true");
- props.set(ClusterParameters.NAME.getName(), "sonarqube");
- ClusterProperties clusterProperties = new ClusterProperties(props);
- clusterProperties.validate();
- }
-
- @Test
- public void test_members() {
- Props props = new Props(new Properties());
- props.set(ClusterParameters.ENABLED.getName(), "true");
-
- assertThat(
- retrieveMembers(props)
- ).isEqualTo(
- Collections.emptyList()
- );
-
- props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.1");
- assertThat(
- retrieveMembers(props)
- ).isEqualTo(
- Arrays.asList("192.168.1.1:" + ClusterParameters.PORT.getDefaultValue())
- );
-
- props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.2:5501");
- assertThat(
- retrieveMembers(props)
- ).containsExactlyInAnyOrder(
- "192.168.1.2:5501"
- );
-
- props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.2:5501,192.168.1.1");
- assertThat(
- retrieveMembers(props)
- ).containsExactlyInAnyOrder(
- "192.168.1.2:5501", "192.168.1.1:" + ClusterParameters.PORT.getDefaultValue()
- );
- }
-
- @Test
- public void test_cluster_properties() {
- Props props = new Props(new Properties());
- props.set(ClusterParameters.ENABLED.getName(), "true");
- props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.1");
- props.set(ClusterParameters.INTERFACES.getName(), "192.168.1.30");
- props.set(ClusterParameters.PORT.getName(), "9003");
- props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), "INFO");
- props.set(ClusterParameters.PORT_AUTOINCREMENT.getName(), "false");
- props.set(ClusterParameters.NAME.getName(), "sonarqube");
-
- ClusterProperties clusterProperties = new ClusterProperties(props);
- clusterProperties.populateProps(props);
-
- assertThat(props.rawProperties()).containsOnly(
- entry(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), "INFO"),
- entry(ClusterParameters.PORT.getName(), "9003"),
- entry(ClusterParameters.ENABLED.getName(), "true"),
- entry(ClusterParameters.INTERFACES.getName(), "192.168.1.30"),
- entry(ClusterParameters.MEMBERS.getName(), "192.168.1.1:" + ClusterParameters.PORT.getDefaultValue()),
- entry(ClusterParameters.NAME.getName(), "sonarqube"),
- entry(ClusterParameters.PORT_AUTOINCREMENT.getName(), "false")
- );
- }
-
- private List<String> retrieveMembers(Props props) {
- return new ClusterProperties(props).getMembers();
- }
-}
diff --git a/sonar-application/src/test/java/org/sonar/application/ClusterTest.java b/sonar-application/src/test/java/org/sonar/application/ClusterTest.java
deleted file mode 100644
index f31b01ba2ac..00000000000
--- a/sonar-application/src/test/java/org/sonar/application/ClusterTest.java
+++ /dev/null
@@ -1,164 +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 com.hazelcast.core.HazelcastException;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.NetworkInterface;
-import java.net.ServerSocket;
-import java.net.SocketException;
-import java.util.Enumeration;
-import java.util.Properties;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
-
-public class ClusterTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void test_cluster() throws Exception {
- Properties properties = new Properties();
- properties.put(ClusterParameters.ENABLED.getName(), "true");
- ClusterProperties clusterProperties = toClusterProperties(properties);
-
- try (Cluster cluster = new Cluster(clusterProperties)) {
- assertThat(
- cluster.isActive()
- ).isEqualTo(true);
- }
-
- properties.put(ClusterParameters.ENABLED.getName(), "false");
- clusterProperties = toClusterProperties(properties);
- try (Cluster cluster = new Cluster(clusterProperties)) {
- assertThat(
- cluster.isActive()
- ).isEqualTo(false);
- }
- }
-
- @Test
- public void test_interface() throws Exception {
- String ipAddress = findIPv4Address().getHostAddress();
- int port = findAvailablePort();
-
- Properties properties = new Properties();
- properties.put(ClusterParameters.INTERFACES.getName(), ipAddress);
- properties.put(ClusterParameters.PORT.getName(), Integer.toString(port));
- properties.put(ClusterParameters.ENABLED.getName(), "true");
- ClusterProperties clusterProperties = toClusterProperties(properties);
-
- try (Cluster cluster = new Cluster(clusterProperties)) {
- assertThat(
- cluster.hazelcastInstance.getConfig().getNetworkConfig().getInterfaces().isEnabled()
- ).isEqualTo(true);
- assertThat(
- cluster.hazelcastInstance.getConfig().getNetworkConfig().getInterfaces().getInterfaces()
- ).containsExactly(ipAddress);
- InetSocketAddress localSocket = (InetSocketAddress) cluster.hazelcastInstance.getLocalEndpoint().getSocketAddress();
- assertThat(
- (localSocket).getPort()
- ).isEqualTo(port);
- assertThat(
- (localSocket).getAddress().getHostAddress()
- ).isEqualTo(ipAddress);
- }
- }
-
- @Test
- public void test_with_already_used_port() throws IOException {
- InetAddress ipAddress = findIPv4Address();
-
- Cluster cluster = null;
-
- try (ServerSocket socket = new ServerSocket(0, 50, ipAddress)) {
- Properties properties = new Properties();
- properties.put(ClusterParameters.INTERFACES.getName(), ipAddress.getHostAddress());
- properties.put(ClusterParameters.PORT.getName(), Integer.toString(socket.getLocalPort()));
- properties.put(ClusterParameters.ENABLED.getName(), "true");
- ClusterProperties clusterProperties = toClusterProperties(properties);
-
- expectedException.expect(HazelcastException.class);
- cluster = new Cluster(clusterProperties);
- } finally {
- if (cluster != null) {
- cluster.close();
- }
- }
- }
-
- @Test
- public void test_adding_port_to_members() throws Exception {
- InetAddress ipAddress = findIPv4Address();
-
- Properties properties = new Properties();
- properties.put(ClusterParameters.ENABLED.getName(), "true");
- properties.put(ClusterParameters.MEMBERS.getName(), ipAddress.getHostAddress());
- ClusterProperties clusterProperties = toClusterProperties(properties);
-
- try (Cluster cluster = new Cluster(clusterProperties)) {
- assertThat(
- cluster.hazelcastInstance.getConfig().getNetworkConfig().getJoin().getTcpIpConfig().getMembers()
- ).containsExactly(ipAddress.getHostAddress() + ":9003");
- }
- }
-
- private ClusterProperties toClusterProperties(Properties properties) {
- return new ClusterProperties(new Props(properties));
- }
-
- private InetAddress findIPv4Address() throws SocketException {
- Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
-
- while (interfaces.hasMoreElements()) {
- NetworkInterface networkInterface = interfaces.nextElement();
- if (!networkInterface.isVirtual()
- && !networkInterface.isLoopback()
- && !networkInterface.getName().startsWith("docker")) {
- Enumeration<InetAddress> ips = networkInterface.getInetAddresses();
- while (ips.hasMoreElements()) {
- InetAddress ip = ips.nextElement();
-
- if (ip instanceof Inet4Address) {
- return ip;
- }
- }
- }
- }
-
- fail("Missing IPv4 address");
- return null;
- }
-
- private int findAvailablePort() throws IOException {
- try (ServerSocket ignored = new ServerSocket(0)) {
- return ignored.getLocalPort();
- }
- }
-}
diff --git a/sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java b/sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java
deleted file mode 100644
index fc3bb457846..00000000000
--- a/sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java
+++ /dev/null
@@ -1,407 +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.util.Map;
-import java.util.Properties;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-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.ProcessId;
-import org.sonar.process.Props;
-import org.sonar.process.monitor.JavaCommand;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-
-public class JavaCommandFactoryImplTest {
- private static final String SEARCH_JAVA_OPTS = "sonar.search.javaOpts";
- private static final String SEARCH_JAVA_ADDITIONAL_OPTS = "sonar.search.javaAdditionalOpts";
- private static final String WEB_JAVA_OPTS = "sonar.web.javaOpts";
- private static final String WEB_JAVA_ADDITIONAL_OPTS = "sonar.web.javaAdditionalOpts";
- private static final String CE_JAVA_OPTS = "sonar.ce.javaOpts";
- private static final String CE_JAVA_ADDITIONAL_OPTS = "sonar.ce.javaAdditionalOpts";
- private static final String PATH_LOGS = "sonar.path.logs";
- private static final String JDBC_DRIVER_PATH = "sonar.jdbc.driverPath";
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private File homeDir;
- private JavaCommandFactoryImpl underTest = new JavaCommandFactoryImpl();
-
- @Before
- public void setUp() throws Exception {
- homeDir = temp.newFolder();
- }
-
- @Test
- public void createEsCommand_fails_if_search_javaOpts_property_is_not_set() {
- expectMissingPropertyIAE(SEARCH_JAVA_OPTS);
-
- underTest.createESCommand(newProps(), homeDir);
- }
-
- @Test
- public void createEsCommand_fails_if_search_javaAdditionalOpts_property_is_not_set() {
- expectMissingPropertyIAE(SEARCH_JAVA_ADDITIONAL_OPTS);
-
- underTest.createESCommand(newProps(SEARCH_JAVA_OPTS, "foo"), homeDir);
- }
-
- @Test
- public void createEsCommand_sets_SearchServer_for_className() {
- JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getClassName()).isEqualTo("org.sonar.search.SearchServer");
- }
-
- @Test
- public void createESCommand_puts_common_and_search_lib_directories_in_classpath() {
- JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getClasspath()).containsOnly("./lib/common/*", "./lib/search/*");
- }
-
- @Test
- public void createESCommand_adds_headless_java_option() {
- JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getJavaOptions()).contains("-Djava.awt.headless=true");
- }
-
- @Test
- public void createESCommand_adds_search_javaOpts_and_javaAdditionalOpts_java_options() {
- JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getJavaOptions()).contains(mockValueFor(SEARCH_JAVA_OPTS), mockValueFor(SEARCH_JAVA_ADDITIONAL_OPTS));
- }
-
- @Test
- public void createESCommand_sets_ES_processId() {
- JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getProcessId()).isSameAs(ProcessId.ELASTICSEARCH);
- }
-
- @Test
- public void createESCommand_sets_workdir_to_argument() {
- JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getWorkDir()).isSameAs(homeDir);
- }
-
- @Test
- public void createESCommand_add_options_for_http_and_https_proxies_from_props() throws Exception {
- addOptionsForHttpAndHttpsProxiesFromProps((props, fileDir) -> underTest.createESCommand(props, fileDir));
- }
-
- @Test
- public void createESCommand_use_http_properties_from_props_as_defaults_for_https_properties() throws Exception {
- userHttpPropertiesFromPropsAsDefaultForHttpsProperties((props, file) -> underTest.createESCommand(props, file));
- }
-
- @Test
- public void createEsCommand_add_no_proxy_option_if_no_proxy_property_in_props() throws Exception {
- noProxyOptionIfNoProxyPropertyInProps((props, file) -> underTest.createESCommand(props, file));
- }
-
- @Test
- public void createEsCommand_passes_rawProperties_of_Props_argument_as_argument_of_javaCommand() {
- passesRawPropertiesOfPropsAsArgumentsOfJavaCommand((props, fileDir) -> underTest.createESCommand(props, fileDir));
- }
-
- @Test
- public void createWebCommand_fails_if_web_javaOpts_property_is_not_set() {
- expectMissingPropertyIAE(WEB_JAVA_OPTS);
-
- underTest.createWebCommand(newProps(), homeDir);
- }
-
- @Test
- public void createWebCommand_fails_if_web_javaAdditionalOpts_property_is_not_set() {
- expectMissingPropertyIAE(WEB_JAVA_ADDITIONAL_OPTS);
-
- underTest.createWebCommand(newProps(WEB_JAVA_OPTS, "foo"), homeDir);
- }
-
- @Test
- public void createWebCommand_fails_if_log_dir_path_property_is_not_set() {
- expectMissingPropertyIAE(PATH_LOGS);
-
- underTest.createWebCommand(newProps(WEB_JAVA_OPTS, "foo", WEB_JAVA_ADDITIONAL_OPTS, "bar"), homeDir);
- }
-
- @Test
- public void createWebCommand_sets_SearchServer_for_className() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getClassName()).isEqualTo("org.sonar.server.app.WebServer");
- }
-
- @Test
- public void createWebCommand_puts_common_and_server_lib_directories_in_classpath() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getClasspath()).containsOnly("./lib/common/*", "./lib/server/*");
- }
-
- @Test
- public void createWebCommand_adds_jdbc_driver_to_classpath_if_property_is_set_in_props() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(JDBC_DRIVER_PATH, "foo"), homeDir);
-
- assertThat(javaCommand.getClasspath()).contains("foo");
- }
-
- @Test
- public void createWebCommand_set_env_variable_for_path_to_log_dir() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getEnvVariables()).contains(entry("sonar.path.logs", mockValueFor(PATH_LOGS)));
- }
-
- @Test
- public void createWebCommand_adds_search_javaOpts_and_javaAdditionalOpts_java_options() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getJavaOptions()).contains(mockValueFor(WEB_JAVA_OPTS), mockValueFor(WEB_JAVA_ADDITIONAL_OPTS));
- }
-
- @Test
- public void createWebCommand_sets_headless_and_encoding_java_options() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getJavaOptions()).contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8");
- }
-
- @Test
- public void createWebCommand_sets_WEB_SERVER_processId() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getProcessId()).isSameAs(ProcessId.WEB_SERVER);
- }
-
- @Test
- public void createWebCommand_sets_workdir_to_argument() {
- JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getWorkDir()).isSameAs(homeDir);
- }
-
- @Test
- public void createWebCommand_add_options_fore_http_and_https_proxies_from_props() throws Exception {
- addOptionsForHttpAndHttpsProxiesFromProps((props, fileDir) -> underTest.createWebCommand(props, fileDir));
- }
-
- @Test
- public void createWebCommand_use_http_properties_from_props_as_defaults_for_https_properties() throws Exception {
- userHttpPropertiesFromPropsAsDefaultForHttpsProperties((props, file) -> underTest.createWebCommand(props, file));
- }
-
- @Test
- public void createWebCommand_add_no_proxy_option_if_no_proxy_property_in_props() throws Exception {
- noProxyOptionIfNoProxyPropertyInProps((props, file) -> underTest.createWebCommand(props, file));
- }
-
- @Test
- public void createWebCommand_passes_rawProperties_of_Props_argument_as_argument_of_javaCommand() {
- passesRawPropertiesOfPropsAsArgumentsOfJavaCommand((props, fileDir) -> underTest.createWebCommand(props, fileDir));
- }
-
- @Test
- public void createCeCommand_fails_if_web_javaOpts_property_is_not_set() {
- expectMissingPropertyIAE(CE_JAVA_OPTS);
-
- underTest.createCeCommand(newProps(), homeDir);
- }
-
- @Test
- public void createCeCommand_fails_if_web_javaAdditionalOpts_property_is_not_set() {
- expectMissingPropertyIAE(CE_JAVA_ADDITIONAL_OPTS);
-
- underTest.createCeCommand(newProps(CE_JAVA_OPTS, "foo"), homeDir);
- }
-
- @Test
- public void createCeCommand_sets_SearchServer_for_className() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getClassName()).isEqualTo("org.sonar.ce.app.CeServer");
- }
-
- @Test
- public void createCeCommand_puts_common_server_and_ce_lib_directories_in_classpath() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getClasspath()).containsOnly("./lib/common/*", "./lib/server/*", "./lib/ce/*");
- }
-
- @Test
- public void createCeCommand_adds_jdbc_driver_to_classpath_if_property_is_set_in_props() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(JDBC_DRIVER_PATH, "foo"), homeDir);
-
- assertThat(javaCommand.getClasspath()).contains("foo");
- }
-
- @Test
- public void createCeCommand_sets_headless_and_encoding_java_options() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getJavaOptions()).contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8");
- }
-
- @Test
- public void createCeCommand_adds_search_javaOpts_and_javaAdditionalOpts_java_options() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getJavaOptions()).contains(mockValueFor(CE_JAVA_OPTS), mockValueFor(CE_JAVA_ADDITIONAL_OPTS));
- }
-
- @Test
- public void createCeCommand_sets_workdir_to_argument() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getWorkDir()).isSameAs(homeDir);
- }
-
- @Test
- public void createCeCommand_sets_COMPUTE_ENGINE_processId() {
- JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(javaCommand.getProcessId()).isSameAs(ProcessId.COMPUTE_ENGINE);
- }
-
- @Test
- public void createCeCommand_add_options_for_http_and_https_proxies_from_props() throws Exception {
- addOptionsForHttpAndHttpsProxiesFromProps((props, fileDir) -> underTest.createCeCommand(props, fileDir));
- }
-
- @Test
- public void createCeCommand_use_http_properties_from_props_as_defaults_for_https_properties() throws Exception {
- userHttpPropertiesFromPropsAsDefaultForHttpsProperties((props, file) -> underTest.createCeCommand(props, file));
- }
-
- @Test
- public void createCeCommand_passes_rawProperties_of_Props_argument_as_argument_of_javaCommand() {
- passesRawPropertiesOfPropsAsArgumentsOfJavaCommand((props, fileDir) -> underTest.createCeCommand(props, fileDir));
- }
-
- private void addOptionsForHttpAndHttpsProxiesFromProps(BiFunction<Props, File, JavaCommand> callCreateMethod) {
- Props props = newPropsWithRequiredProperties();
-
- // These properties can be defined in conf/sonar.properties.
- // They must be propagated to JVM.
- props.set("http.proxyHost", "1.2.3.4");
- props.set("http.proxyPort", "80");
- props.set("https.proxyHost", "5.6.7.8");
- props.set("https.proxyPort", "443");
-
- JavaCommand command = callCreateMethod.apply(props, homeDir);
- assertThat(command.getJavaOptions()).contains("-Dhttp.proxyHost=1.2.3.4");
- assertThat(command.getJavaOptions()).contains("-Dhttp.proxyPort=80");
- assertThat(command.getJavaOptions()).contains("-Dhttps.proxyHost=5.6.7.8");
- assertThat(command.getJavaOptions()).contains("-Dhttps.proxyPort=443");
- }
-
- @Test
- public void createCeCommand_add_no_proxy_option_if_no_proxy_property_in_props() throws Exception {
- noProxyOptionIfNoProxyPropertyInProps((props, file) -> underTest.createCeCommand(props, file));
- }
-
- private void userHttpPropertiesFromPropsAsDefaultForHttpsProperties(BiFunction<Props, File, JavaCommand> callCreateMethod) {
- Props props = newPropsWithRequiredProperties();
- props.set("http.proxyHost", "1.2.3.4");
- props.set("http.proxyPort", "80");
-
- JavaCommand command = callCreateMethod.apply(props, homeDir);
- assertThat(command.getJavaOptions()).contains("-Dhttp.proxyHost=1.2.3.4");
- assertThat(command.getJavaOptions()).contains("-Dhttp.proxyPort=80");
- assertThat(command.getJavaOptions()).contains("-Dhttps.proxyHost=1.2.3.4");
- assertThat(command.getJavaOptions()).contains("-Dhttps.proxyPort=80");
- }
-
- private void passesRawPropertiesOfPropsAsArgumentsOfJavaCommand(BiFunction<Props, File, JavaCommand> callCreateMethod) {
- Props props = newPropsWithRequiredProperties("cryptedProperty", "{AES}AAAAA");
- JavaCommand javaCommand = callCreateMethod.apply(props, homeDir);
-
- Map<String, String> rawProperties = (Map<String, String>) ((Map) props.rawProperties());
- assertThat(javaCommand.getArguments()).containsAllEntriesOf(rawProperties);
- }
-
- private void noProxyOptionIfNoProxyPropertyInProps(BiFunction<Props, File, JavaCommand> callCreateMethod) {
- JavaCommand command = callCreateMethod.apply(newPropsWithRequiredProperties(), homeDir);
-
- assertThat(command.getJavaOptions()).doesNotContain("http.proxyHost");
- assertThat(command.getJavaOptions()).doesNotContain("https.proxyHost");
- assertThat(command.getJavaOptions()).doesNotContain("http.proxyPort");
- assertThat(command.getJavaOptions()).doesNotContain("https.proxyPort");
- }
-
- private void expectMissingPropertyIAE(String property) {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Missing property: " + property);
- }
-
- private Props newPropsWithRequiredProperties(String... properties) {
- return newProps(
- (props) -> addProperties(props, properties),
- SEARCH_JAVA_OPTS, mockValueFor(SEARCH_JAVA_OPTS),
- SEARCH_JAVA_ADDITIONAL_OPTS, mockValueFor(SEARCH_JAVA_ADDITIONAL_OPTS),
- WEB_JAVA_OPTS, mockValueFor(WEB_JAVA_OPTS),
- WEB_JAVA_ADDITIONAL_OPTS, mockValueFor(WEB_JAVA_ADDITIONAL_OPTS),
- PATH_LOGS, mockValueFor(PATH_LOGS),
- CE_JAVA_OPTS, mockValueFor(CE_JAVA_OPTS),
- CE_JAVA_ADDITIONAL_OPTS, mockValueFor(CE_JAVA_ADDITIONAL_OPTS));
- }
-
- private static String mockValueFor(String str) {
- return str + "_value";
- }
-
- private Props newProps(String... properties) {
- return newProps((props) -> {
- }, properties);
- }
-
- private Props newProps(Consumer<Properties> extraConf, String... properties) {
- Properties props = new Properties();
- addProperties(props, properties);
- extraConf.accept(props);
- return new Props(props);
- }
-
- private void addProperties(Properties props, String[] properties) {
- if (properties.length % 2 != 0) {
- throw new IllegalArgumentException("Properties must all have key and value");
- }
- for (int i = 0; i < properties.length; i++) {
- props.setProperty(properties[i], properties[i + 1]);
- i++;
- }
- }
-
-}
diff --git a/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java b/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java
deleted file mode 100644
index 9833c329b41..00000000000
--- a/sonar-application/src/test/java/org/sonar/application/PropsBuilderTest.java
+++ /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.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-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.TemporaryFolder;
-import org.sonar.process.Props;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class PropsBuilderTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- private File homeDir;
- private JdbcSettings jdbcSettings = mock(JdbcSettings.class);
-
- @Before
- public void before() throws IOException {
- homeDir = temp.newFolder();
- }
-
- @Test
- public void build_props() throws Exception {
- Properties rawProperties = new Properties();
- rawProperties.setProperty("foo", "bar");
-
- Props props = new PropsBuilder(rawProperties, jdbcSettings, homeDir).build();
-
- assertThat(props.value("foo")).isEqualTo("bar");
- assertThat(props.value("unknown")).isNull();
-
- // default properties
- assertThat(props.valueAsInt("sonar.search.port")).isEqualTo(9001);
- }
-
- @Test
- public void load_properties_file_if_exists() throws Exception {
- FileUtils.write(new File(homeDir, "conf/sonar.properties"), "sonar.jdbc.username=angela\nsonar.origin=file");
-
- Properties rawProperties = new Properties();
- rawProperties.setProperty("sonar.origin", "raw");
- Props props = new PropsBuilder(rawProperties, jdbcSettings, homeDir).build();
-
- // properties loaded from file
- assertThat(props.value("sonar.jdbc.username")).isEqualTo("angela");
-
- // command-line arguments override sonar.properties file
- assertThat(props.value("sonar.origin")).isEqualTo("raw");
- }
-
- @Test
- public void utf8_file_encoding() throws Exception {
- FileUtils.write(new File(homeDir, "conf/sonar.properties"), "utf8prop=Thônes", StandardCharsets.UTF_8);
- Props props = new PropsBuilder(new Properties(), jdbcSettings, homeDir).build();
- assertThat(props.value("utf8prop")).isEqualTo("Thônes");
- }
-
- @Test
- public void do_not_load_properties_file_if_not_exists() throws Exception {
- Properties rawProperties = new Properties();
- rawProperties.setProperty("sonar.origin", "raw");
- Props props = new PropsBuilder(rawProperties, jdbcSettings, homeDir).build();
-
- assertThat(props.value("sonar.origin")).isEqualTo("raw");
- }
-
- @Test
- public void detectHomeDir() throws Exception {
- assertThat(PropsBuilder.detectHomeDir()).isDirectory().exists();
-
- }
-}
diff --git a/sonar-application/src/test/resources/conf/sonar.properties b/sonar-application/src/test/resources/conf/sonar.properties
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/sonar-application/src/test/resources/conf/sonar.properties
+++ /dev/null
diff --git a/tests/upgrade/src/test/java/org/sonarsource/sonarqube/upgrade/UpgradeTest.java b/tests/upgrade/src/test/java/org/sonarsource/sonarqube/upgrade/UpgradeTest.java
index 29f7c0899e7..59d5abde857 100644
--- a/tests/upgrade/src/test/java/org/sonarsource/sonarqube/upgrade/UpgradeTest.java
+++ b/tests/upgrade/src/test/java/org/sonarsource/sonarqube/upgrade/UpgradeTest.java
@@ -204,7 +204,7 @@ public class UpgradeTest {
.setOrchestratorProperty("orchestrator.keepDatabase", "true")
.setOrchestratorProperty("javaVersion", LATEST_JAVA_RELEASE)
.addPlugin("java")
- .setStartupLogWatcher(log -> log.contains("Process[web] is up"));
+ .setStartupLogWatcher(log -> log.contains("Database must be upgraded"));
orchestrator = builder.build();
orchestrator.start();
initSelenide(orchestrator);