aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-tests/src/test/java/it/Category5Suite.java2
-rw-r--r--it/it-tests/src/test/java/it/serverSystem/ClusterTest.java125
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java16
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java17
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/LogServerVersion.java64
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java186
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java8
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadata.java48
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java61
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataProvider.java84
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/UrlSettings.java106
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java36
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java16
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java14
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java29
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/ServerMetadataPersister.java54
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java78
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java303
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java57
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java137
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/UrlSettingsTest.java199
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java35
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel1Test.java45
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java88
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/startup/ServerMetadataPersisterTest.java66
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java2
-rw-r--r--sonar-application/src/main/java/org/sonar/application/App.java2
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AppTest.java16
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java7
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java4
38 files changed, 1267 insertions, 678 deletions
diff --git a/it/it-tests/src/test/java/it/Category5Suite.java b/it/it-tests/src/test/java/it/Category5Suite.java
index 4f87a8880bc..1e3af4ff8c2 100644
--- a/it/it-tests/src/test/java/it/Category5Suite.java
+++ b/it/it-tests/src/test/java/it/Category5Suite.java
@@ -19,6 +19,7 @@
*/
package it;
+import it.serverSystem.ClusterTest;
import it.serverSystem.RestartTest;
import it.serverSystem.ServerSystemRestartingOrchestrator;
import it.settings.SettingsTestRestartingOrchestrator;
@@ -34,6 +35,7 @@ import org.junit.runners.Suite;
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
+ ClusterTest.class,
ServerSystemRestartingOrchestrator.class,
RestartTest.class,
SettingsTestRestartingOrchestrator.class,
diff --git a/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java b/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java
new file mode 100644
index 00000000000..c0a9a887104
--- /dev/null
+++ b/it/it-tests/src/test/java/it/serverSystem/ClusterTest.java
@@ -0,0 +1,125 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package it.serverSystem;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.sonar.orchestrator.Orchestrator;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.io.FileUtils;
+import org.junit.Test;
+import org.sonarqube.ws.client.rule.SearchWsRequest;
+import util.ItUtils;
+
+import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.newWsClient;
+
+public class ClusterTest {
+
+ private static final String CONF_FILE_PATH = "conf/sonar.properties";
+
+ /**
+ * SONAR-7899
+ */
+ @Test
+ public void secondary_nodes_do_not_write_to_datastores_at_startup() throws Exception {
+ // start "startup leader", which creates and populates datastores
+ Orchestrator orchestrator = Orchestrator.builderEnv()
+ .setServerProperty("sonar.cluster.enabled", "true")
+ .setServerProperty("sonar.cluster.startupLeader", "true")
+ .setServerProperty("sonar.log.level", "TRACE")
+ .addPlugin(ItUtils.xooPlugin())
+ .build();
+ orchestrator.start();
+
+ expectLog(orchestrator, "Cluster enabled (startup leader)");
+ expectWriteOperations(orchestrator, true);
+ // verify that datastores are populated by requesting rules
+ assertThat(newWsClient(orchestrator).rules().search(new SearchWsRequest()).getTotal()).isGreaterThan(0);
+
+ FileUtils.write(orchestrator.getServer().getLogs(), "", false);
+ updateSonarPropertiesFile(orchestrator, ImmutableMap.of("sonar.cluster.startupLeader", "false"));
+ orchestrator.restartServer();
+
+ expectLog(orchestrator, "Cluster enabled (startup follower)");
+ expectWriteOperations(orchestrator, false);
+
+ orchestrator.stop();
+ }
+
+ private static void expectLog(Orchestrator orchestrator, String expectedLog) throws IOException {
+ File logFile = orchestrator.getServer().getLogs();
+ try (Stream<String> lines = Files.lines(logFile.toPath())) {
+ assertThat(lines.anyMatch(s -> containsIgnoreCase(s, expectedLog))).isTrue();
+ }
+ }
+
+ private static void expectWriteOperations(Orchestrator orchestrator, boolean expected) throws IOException {
+ try (Stream<String> lines = Files.lines(orchestrator.getServer().getLogs().toPath())) {
+ List<String> writeOperations = lines.filter(ClusterTest::isWriteOperation).collect(Collectors.toList());
+ if (expected) {
+ assertThat(writeOperations).isNotEmpty();
+ } else {
+ assertThat(writeOperations).as("Unexpected write operations: " + Joiner.on('\n').join(writeOperations)).isEmpty();
+
+ }
+ }
+ }
+
+ private static boolean isWriteOperation(String log) {
+ return isDbWriteOperation(log) || isEsWriteOperation(log);
+ }
+
+ private static boolean isDbWriteOperation(String log) {
+ return log.contains("web[sql]") && (containsIgnoreCase(log, "sql=insert") ||
+ containsIgnoreCase(log, "sql=update") ||
+ containsIgnoreCase(log, "sql=delete") ||
+ containsIgnoreCase(log, "sql=create"));
+ }
+
+ private static boolean isEsWriteOperation(String log) {
+ return log.contains("web[es]") && (containsIgnoreCase(log, "Create index") ||
+ containsIgnoreCase(log, "Create type") ||
+ containsIgnoreCase(log, "put mapping request") ||
+ containsIgnoreCase(log, "refresh request") ||
+ containsIgnoreCase(log, "index request"));
+ }
+
+ private static void updateSonarPropertiesFile(Orchestrator orchestrator, Map<String, String> props) throws IOException {
+ Properties propsFile = new Properties();
+ try (FileInputStream conf = FileUtils.openInputStream(new File(orchestrator.getServer().getHome(), CONF_FILE_PATH))) {
+ propsFile.load(conf);
+ propsFile.putAll(props);
+ }
+ try (FileOutputStream conf = FileUtils.openOutputStream(new File(orchestrator.getServer().getHome(), CONF_FILE_PATH))) {
+ propsFile.store(conf, "");
+ }
+ }
+}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index bab0a7282d1..571a58602e6 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -105,7 +105,11 @@ import org.sonar.server.platform.ServerFileSystemImpl;
import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.ServerLifecycleNotifier;
import org.sonar.server.platform.ServerLogging;
+import org.sonar.server.platform.StartupMetadataProvider;
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.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.ServerExtensionInstaller;
import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
@@ -150,7 +154,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
.add(props.rawProperties())
.add(level1Components())
.add(toArray(CorePropertyDefinitions.all()))
- .add(toArray(CePropertyDefinitions.all()));
+ .add(toArray(CePropertyDefinitions.all()))
+ .add(toArray(ClusterProperties.definitions()));
configureFromModules(this.level1);
this.level1.startComponents();
@@ -205,9 +210,9 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
ComputeEngineSettings.class,
new SonarQubeVersion(apiVersion),
SonarRuntimeImpl.forSonarQube(ApiVersion.load(System2.INSTANCE), SonarQubeSide.COMPUTE_ENGINE),
- ServerImpl.class,
UuidFactoryImpl.INSTANCE,
- // no EmbeddedDatabaseFactory.class, creating H2 DB if responsibility of WebServer
+ UrlSettings.class,
+ ClusterImpl.class,
DefaultDatabase.class,
DatabaseChecker.class,
// must instantiate deprecated class in 5.2 and only this one (and not its replacement)
@@ -273,11 +278,10 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
private static Object[] level3Components() {
return new Object[] {
+ new StartupMetadataProvider(),
PersistentSettings.class,
- // ServerMetadataPersister.class, server id is the responsibility of Web Server
- // DefaultHttpDownloader.class, does not make sense to use it from Compute Engine
UriReader.class,
- // ServerIdGenerator.class, server id is the responsibility of Web Server
+ ServerImpl.class
};
}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index aa67805417f..dd5d7c14038 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -21,16 +21,17 @@ package org.sonar.ce.container;
import java.io.File;
import java.io.IOException;
-import java.util.Date;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.picocontainer.MutablePicoContainer;
+import org.sonar.api.CoreProperties;
import org.sonar.api.database.DatabaseProperties;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
@@ -41,7 +42,6 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
import static org.sonar.process.ProcessProperties.PATH_DATA;
import static org.sonar.process.ProcessProperties.PATH_HOME;
import static org.sonar.process.ProcessProperties.PATH_TEMP;
-import static org.sonar.process.ProcessProperties.STARTED_AT;
public class ComputeEngineContainerImplTest {
private static final int CONTAINER_ITSELF = 1;
@@ -65,7 +65,6 @@ public class ComputeEngineContainerImplTest {
File homeDir = tempFolder.newFolder();
File dataDir = new File(homeDir, "data");
File tmpDir = new File(homeDir, "tmp");
- properties.setProperty(STARTED_AT, valueOf(new Date().getTime()));
properties.setProperty(PATH_HOME, homeDir.getAbsolutePath());
properties.setProperty(PATH_DATA, dataDir.getAbsolutePath());
properties.setProperty(PATH_TEMP, tmpDir.getAbsolutePath());
@@ -75,6 +74,8 @@ public class ComputeEngineContainerImplTest {
properties.setProperty(DatabaseProperties.PROP_URL, url);
properties.setProperty(DatabaseProperties.PROP_USER, "sonar");
properties.setProperty(DatabaseProperties.PROP_PASSWORD, "sonar");
+ insertProperty(CoreProperties.SERVER_ID, "a_startup_id");
+ insertProperty("sonar.core.startedAt", "123456789");
underTest
.start(new Props(properties));
@@ -91,7 +92,7 @@ public class ComputeEngineContainerImplTest {
);
assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
- + 2 // level 3
+ + 4 // level 3
);
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
@@ -99,7 +100,7 @@ public class ComputeEngineContainerImplTest {
);
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
- + 23 // level 1
+ + 26 // level 1
+ 46 // content of DaoModule
+ 1 // content of EsSearchModule
+ 55 // content of CorePropertyDefinitions
@@ -112,4 +113,10 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getLifecycleState().isStopped()).isFalse();
assertThat(picoContainer.getLifecycleState().isDisposed()).isTrue();
}
+
+ private void insertProperty(String key, String value) {
+ PropertyDto dto = new PropertyDto().setKey(key).setValue(value);
+ dbTester.getDbClient().propertiesDao().insertProperty(dbTester.getSession(), dto);
+ dbTester.commit();
+ }
}
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 2072c7d212d..49f26c79ab0 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
@@ -27,8 +27,6 @@ import java.util.Map;
* They are almost all the properties defined in conf/sonar.properties.
*/
public class ProcessProperties {
- public static final String STARTED_AT = "sonar.core.startedAt";
-
public static final String CLUSTER_ACTIVATE = "sonar.cluster.activate";
public static final String CLUSTER_MASTER = "sonar.cluster.master";
public static final String CLUSTER_MASTER_HOST = "sonar.cluster.masterHost";
diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java
index ec944405c83..53af7750c8c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java
@@ -27,9 +27,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.picocontainer.Startable;
-import org.sonar.api.server.ServerSide;
-@ServerSide
public abstract class BaseIndexer implements Startable {
private final ThreadPoolExecutor executor;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/LogServerVersion.java b/server/sonar-server/src/main/java/org/sonar/server/platform/LogServerVersion.java
new file mode 100644
index 00000000000..9f84601e116
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/LogServerVersion.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import com.google.common.base.Joiner;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.Startable;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.Version;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+@ServerSide
+public class LogServerVersion implements Startable {
+
+ private static final Logger LOG = Loggers.get(LogServerVersion.class);
+ private final SonarRuntime runtime;
+
+ public LogServerVersion(SonarRuntime runtime) {
+ this.runtime = runtime;
+ }
+
+ @Override
+ public void start() {
+ String scmRevision = read("/build.properties").getProperty("Implementation-Build");
+ Version version = runtime.getApiVersion();
+ LOG.info("SonarQube {}", Joiner.on(" / ").skipNulls().join("Server", version, scmRevision));
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+
+ private static Properties read(String filePath) {
+ try (InputStream stream = LogServerVersion.class.getResourceAsStream(filePath)) {
+ Properties properties = new Properties();
+ properties.load(stream);
+ return properties;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to read file " + filePath + " from classpath", e);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
index e9a9e714115..222183ce84f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
@@ -19,93 +19,36 @@
*/
package org.sonar.server.platform;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-import com.google.common.io.Resources;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.Properties;
-import javax.annotation.CheckForNull;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.StringUtils;
-import org.picocontainer.Startable;
import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.config.Settings;
import org.sonar.api.platform.Server;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.process.ProcessProperties;
+import org.sonar.api.server.ServerSide;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static org.apache.commons.lang.StringUtils.isEmpty;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.sonar.api.CoreProperties.SERVER_BASE_URL;
-import static org.sonar.server.app.TomcatContexts.PROPERTY_CONTEXT;
-
-public final class ServerImpl extends Server implements Startable {
- private static final String PROPERTY_SONAR_CORE_STARTED_AT = "sonar.core.startedAt";
- private static final int DEFAULT_HTTP_PORT = 80;
- private static final String ALL_IPS_HOST = "0.0.0.0";
-
- private static final Logger LOG = Loggers.get(ServerImpl.class);
- public static final int DEFAULT_PORT = 9000;
+@ComputeEngineSide
+@ServerSide
+public class ServerImpl extends Server {
private final Settings settings;
- private final String buildProperties;
- private final String versionPath;
- private Date startedAt;
- private String id;
- private String version;
- private String implementationBuild;
- private File sonarHome;
- private String contextPath;
-
- public ServerImpl(Settings settings) {
- this(settings, "/build.properties", "/sq-version.txt");
- }
+ private final StartupMetadata state;
+ private final ServerFileSystem fs;
+ private final UrlSettings urlSettings;
+ private final SonarRuntime runtime;
- @VisibleForTesting
- ServerImpl(Settings settings, String buildProperties, String versionPath) {
+ public ServerImpl(Settings settings, StartupMetadata state, ServerFileSystem fs, UrlSettings urlSettings, SonarRuntime runtime) {
this.settings = settings;
- this.buildProperties = buildProperties;
- this.versionPath = versionPath;
- }
-
- @Override
- public void start() {
- try {
- String startedAtString = settings.getString(PROPERTY_SONAR_CORE_STARTED_AT);
- checkState(startedAtString != null, "property %s must be set", PROPERTY_SONAR_CORE_STARTED_AT);
- startedAt = new Date(Long.valueOf(startedAtString));
- id = new SimpleDateFormat("yyyyMMddHHmmss").format(startedAt);
-
- version = readVersion(versionPath);
- implementationBuild = read(buildProperties).getProperty("Implementation-Build");
- sonarHome = new File(settings.getString(ProcessProperties.PATH_HOME));
- if (!sonarHome.isDirectory()) {
- throw new IllegalStateException("SonarQube home directory is not valid");
- }
-
- contextPath = StringUtils.defaultIfBlank(settings.getString(PROPERTY_CONTEXT), "")
- // Remove trailing slashes
- .replaceFirst("(\\/+)$", "");
-
- LOG.info("SonarQube {}", Joiner.on(" / ").skipNulls().join("Server", version, implementationBuild));
-
- } catch (IOException e) {
- throw new IllegalStateException("Can not load metadata", e);
- }
+ this.state = state;
+ this.fs = fs;
+ this.urlSettings = urlSettings;
+ this.runtime = runtime;
}
@Override
- public void stop() {
- // do nothing
+ public String getId() {
+ return state.getStartupId();
}
@Override
@@ -114,125 +57,48 @@ public final class ServerImpl extends Server implements Startable {
}
@Override
- public String getId() {
- return id;
- }
-
- @Override
public String getVersion() {
- return version;
- }
-
- public String getImplementationBuild() {
- return implementationBuild;
+ return runtime.getApiVersion().toString();
}
@Override
public Date getStartedAt() {
- return checkNotNull(startedAt, "start() method has not been called");
+ return new Date(state.getStartedAt());
}
@Override
public File getRootDir() {
- return sonarHome;
+ return fs.getHomeDir();
}
@Override
- @CheckForNull
public File getDeployDir() {
- return null;
+ return fs.getDeployDir();
}
@Override
public String getContextPath() {
- return contextPath;
+ return urlSettings.getContextPath();
}
@Override
public String getPublicRootUrl() {
- return getURL();
+ return urlSettings.getBaseUrl();
}
@Override
public boolean isDev() {
- return settings.getBoolean("sonar.web.dev");
+ return urlSettings.isDev();
}
@Override
public boolean isSecured() {
- return getURL().startsWith("https://");
- }
-
- private static String readVersion(String filename) throws IOException {
- URL url = ServerImpl.class.getResource(filename);
- if (url != null) {
- String version = Resources.toString(url, StandardCharsets.UTF_8);
- if (!StringUtils.isBlank(version)) {
- return StringUtils.deleteWhitespace(version);
- }
- }
- throw new IllegalStateException("Unknown SonarQube version");
- }
-
- private static Properties read(String filename) throws IOException {
- Properties properties = new Properties();
-
- InputStream stream = null;
- try {
- stream = ServerImpl.class.getResourceAsStream(filename);
- if (stream != null) {
- properties.load(stream);
- }
- } finally {
- IOUtils.closeQuietly(stream);
- }
-
- return properties;
+ return urlSettings.isSecured();
}
@Override
public String getURL() {
- String serverBaseUrl = settings.getString(SERVER_BASE_URL);
- if (isEmpty(serverBaseUrl)) {
- return computeUrl();
- }
- return serverBaseUrl;
- }
-
- private String computeUrl() {
- String host = settings.getString("sonar.web.host");
- int port = settings.getInt("sonar.web.port");
- String context = settings.getString("sonar.web.context");
-
- StringBuilder res = new StringBuilder();
- res.append("http://");
- appendHost(host, res);
- appendPort(port, res);
- appendContext(context, res);
-
- return res.toString();
- }
-
- private static void appendHost(String host, StringBuilder res) {
- if (isEmpty(host) || ALL_IPS_HOST.equals(host)) {
- res.append("localhost");
- } else {
- res.append(host);
- }
- }
-
- private static void appendPort(int port, StringBuilder res) {
- if (port < 1) {
- res.append(':').append(DEFAULT_PORT);
- } else if (port != DEFAULT_HTTP_PORT) {
- res.append(':').append(port);
- }
- }
-
- private static void appendContext(String context, StringBuilder res) {
- if (isNotEmpty(context)) {
- res.append(context);
- }
+ return urlSettings.getBaseUrl();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java
index b19d2939f25..f7ddfbc1d63 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java
@@ -38,11 +38,11 @@ import java.util.Properties;
*/
public class ServerSettingsImpl extends Settings implements ServerSettings {
- private final Properties properties;
+ private final Properties bootstrapProperties;
- public ServerSettingsImpl(PropertyDefinitions definitions, Properties properties) {
+ public ServerSettingsImpl(PropertyDefinitions definitions, Properties bootstrapProperties) {
super(definitions);
- this.properties = properties;
+ this.bootstrapProperties = bootstrapProperties;
load(Collections.emptyMap());
// Secret key is loaded from conf/sonar.properties
getEncryption().setPathToSecretKey(getString(CoreProperties.ENCRYPTION_SECRET_KEY_PATH));
@@ -63,7 +63,7 @@ public class ServerSettingsImpl extends Settings implements ServerSettings {
// order is important : the last override the first
addProperties(databaseSettings);
- addProperties(properties);
+ addProperties(bootstrapProperties);
return this;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadata.java b/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadata.java
new file mode 100644
index 00000000000..87739be59aa
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadata.java
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+
+import static java.util.Objects.requireNonNull;
+
+@ComputeEngineSide
+@ServerSide
+@Immutable
+public class StartupMetadata {
+
+ private final String startupId;
+ private final long startedAt;
+
+ public StartupMetadata(String startupId, long startedAt) {
+ this.startupId = requireNonNull(startupId);
+ this.startedAt = startedAt;
+ }
+
+ public String getStartupId() {
+ return startupId;
+ }
+
+ public long getStartedAt() {
+ return startedAt;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java b/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java
new file mode 100644
index 00000000000..7c3017dab6b
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import org.sonar.api.CoreProperties;
+import org.sonar.api.Startable;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.PropertiesDao;
+import org.sonar.db.property.PropertyDto;
+
+/**
+ * The server node marked as "startup leader" generates some information about startup. These
+ * information are loaded by "startup follower" servers and all Compute Engine nodes.
+ *
+ * @see StartupMetadataProvider#load(DbClient)
+ */
+@ServerSide
+public class StartupMetadataPersister implements Startable {
+
+ private final StartupMetadata metadata;
+ private final DbClient dbClient;
+
+ public StartupMetadataPersister(StartupMetadata metadata, DbClient dbClient) {
+ this.metadata = metadata;
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void start() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ PropertiesDao dao = dbClient.propertiesDao();
+ dao.insertProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(metadata.getStartupId()));
+ dao.insertProperty(dbSession, new PropertyDto().setKey(StartupMetadataProvider.PROPERTY_STARTED_AT).setValue(String.valueOf(metadata.getStartedAt())));
+ dbSession.commit();
+ }
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataProvider.java b/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataProvider.java
new file mode 100644
index 00000000000..f1ccde603b0
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataProvider.java
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.cluster.Cluster;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+@ComputeEngineSide
+@ServerSide
+public class StartupMetadataProvider extends ProviderAdapter {
+
+ public static final String PROPERTY_STARTED_AT = "sonar.core.startedAt";
+
+ private StartupMetadata cache = null;
+
+ public StartupMetadata provide(UuidFactory uuidFactory, System2 system, SonarRuntime runtime, Cluster cluster, DbClient dbClient) {
+ if (cache == null) {
+ if (runtime.getSonarQubeSide() == SonarQubeSide.SERVER && cluster.isStartupLeader()) {
+ cache = generate(uuidFactory, system);
+ } else {
+ cache = load(dbClient);
+ }
+ }
+ return cache;
+ }
+
+ /**
+ * Generate a UUID. It is not persisted yet as db structure may not be up-to-date if migrations
+ * have to be executed. This is done later by {@link StartupMetadataPersister}
+ */
+ private static StartupMetadata generate(UuidFactory uuidFactory, System2 system) {
+ String startupId = uuidFactory.create();
+ long startedAt = system.now();
+ return new StartupMetadata(startupId, startedAt);
+ }
+
+ /**
+ * Load from database
+ */
+ private static StartupMetadata load(DbClient dbClient) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ String startupId = selectProperty(dbClient, dbSession, CoreProperties.SERVER_ID);
+ String startedAt = selectProperty(dbClient, dbSession, PROPERTY_STARTED_AT);
+ return new StartupMetadata(startupId, Long.parseLong(startedAt));
+ }
+ }
+
+ private static String selectProperty(DbClient dbClient, DbSession dbSession, String key) {
+ PropertyDto prop = dbClient.propertiesDao().selectGlobalProperty(dbSession, key);
+ checkState(prop != null, "Property %s is missing in database", key);
+ checkState(!isBlank(prop.getValue()), "Property %s is set but empty in database", key);
+ return prop.getValue();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/UrlSettings.java b/server/sonar-server/src/main/java/org/sonar/server/platform/UrlSettings.java
new file mode 100644
index 00000000000..b899b6db7f4
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/UrlSettings.java
@@ -0,0 +1,106 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.config.Settings;
+import org.sonar.api.server.ServerSide;
+
+import static org.apache.commons.lang.StringUtils.defaultIfBlank;
+import static org.apache.commons.lang.StringUtils.isEmpty;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+import static org.sonar.api.CoreProperties.SERVER_BASE_URL;
+import static org.sonar.server.app.TomcatContexts.PROPERTY_CONTEXT;
+
+@ComputeEngineSide
+@ServerSide
+public class UrlSettings {
+
+ private static final int DEFAULT_PORT = 9000;
+ private static final int DEFAULT_HTTP_PORT = 80;
+ private static final String ALL_IPS_HOST = "0.0.0.0";
+
+ private final Settings settings;
+ // cached, so can't change at runtime
+ private final String contextPath;
+
+ public UrlSettings(Settings settings) {
+ this.settings = settings;
+ this.contextPath = defaultIfBlank(settings.getString(PROPERTY_CONTEXT), "")
+ // Remove trailing slashes
+ .replaceFirst("(\\/+)$", "");
+ }
+
+ public String getBaseUrl() {
+ String url = settings.getString(SERVER_BASE_URL);
+ if (isEmpty(url)) {
+ url = computeBaseUrl();
+ }
+ return url;
+ }
+
+ public String getContextPath() {
+ return contextPath;
+ }
+
+ public boolean isDev() {
+ return settings.getBoolean("sonar.web.dev");
+ }
+
+ public boolean isSecured() {
+ return getBaseUrl().startsWith("https://");
+ }
+
+ private String computeBaseUrl() {
+ String host = settings.getString("sonar.web.host");
+ int port = settings.getInt("sonar.web.port");
+ String context = settings.getString("sonar.web.context");
+
+ StringBuilder res = new StringBuilder();
+ res.append("http://");
+ appendHost(host, res);
+ appendPort(port, res);
+ appendContext(context, res);
+
+ return res.toString();
+ }
+
+ private static void appendHost(String host, StringBuilder res) {
+ if (isEmpty(host) || ALL_IPS_HOST.equals(host)) {
+ res.append("localhost");
+ } else {
+ res.append(host);
+ }
+ }
+
+ private static void appendPort(int port, StringBuilder res) {
+ if (port < 1) {
+ res.append(':').append(DEFAULT_PORT);
+ } else if (port != DEFAULT_HTTP_PORT) {
+ res.append(':').append(port);
+ }
+ }
+
+ private static void appendContext(String context, StringBuilder res) {
+ if (isNotEmpty(context)) {
+ res.append(context);
+ }
+ }
+}
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
index 3b44379c826..fe08773dd37 100644
--- 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
@@ -29,6 +29,10 @@ public class ClusterProperties {
public static final String ENABLED = "sonar.cluster.enabled";
public static final String STARTUP_LEADER = "sonar.cluster.startupLeader";
+ private ClusterProperties() {
+ // only statics
+ }
+
public static List<PropertyDefinition> definitions() {
return ImmutableList.of(
PropertyDefinition.builder(ENABLED)
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java
index d060d8bc2c6..4d3d08d6dcf 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java
@@ -21,12 +21,16 @@ package org.sonar.server.platform.platformlevel;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.platform.Module;
+import org.sonar.server.platform.cluster.Cluster;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
public abstract class PlatformLevel {
private final String name;
@@ -105,14 +109,16 @@ public abstract class PlatformLevel {
return this;
}
- protected void add(@Nullable Object object, boolean singleton) {
- if (object != null) {
- container.addComponent(object, singleton);
- }
+ protected <T> T get(Class<T> tClass) {
+ return requireNonNull(container.getComponentByType(tClass));
+ }
+
+ protected <T> List<T> getAll(Class<T> tClass) {
+ return container.getComponentsByType(tClass);
}
- protected <T> T getComponentByType(Class<T> tClass) {
- return container.getComponentByType(tClass);
+ protected <T> Optional<T> getOptional(Class<T> tClass) {
+ return Optional.ofNullable(container.getComponentByType(tClass));
}
protected void add(Object... objects) {
@@ -123,6 +129,24 @@ public abstract class PlatformLevel {
}
}
+ /**
+ * Add a component to container only if the server node is marked as "startupLeader" (cluster disabled
+ * or first node of the cluster to be started).
+ *
+ * @throws IllegalStateException if called from PlatformLevel1, when cluster settings are not loaded
+ */
+ protected void addIfStartupLeader(Object... objects) {
+ Optional<Cluster> cluster = getOptional(Cluster.class);
+ checkState(cluster.isPresent(), "Cluster settings not loaded yet");
+ if (cluster.get().isStartupLeader()) {
+ for (Object object : objects) {
+ if (object != null) {
+ container.addComponent(object, true);
+ }
+ }
+ }
+ }
+
protected void addAll(Collection<?> objects) {
add(objects.toArray(new Object[objects.size()]));
}
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 321feee91b7..fc9045a70f3 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
@@ -40,14 +40,17 @@ import org.sonar.db.semaphore.SemaphoresImpl;
import org.sonar.db.version.DatabaseVersion;
import org.sonar.server.app.ProcessCommandWrapperImpl;
import org.sonar.server.app.RestartFlagHolderImpl;
-import org.sonar.server.platform.db.EmbeddedDatabaseFactory;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.platform.DatabaseServerCompatibility;
-import org.sonar.server.platform.ServerFileSystemImpl;
+import org.sonar.server.platform.LogServerVersion;
import org.sonar.server.platform.Platform;
-import org.sonar.server.platform.ServerImpl;
+import org.sonar.server.platform.ServerFileSystemImpl;
import org.sonar.server.platform.ServerSettingsImpl;
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.ruby.PlatformRackBridge;
import org.sonar.server.rule.index.RuleIndex;
@@ -75,11 +78,12 @@ public class PlatformLevel1 extends PlatformLevel {
add(
new SonarQubeVersion(apiVersion),
SonarRuntimeImpl.forSonarQube(apiVersion, SonarQubeSide.SERVER),
+ LogServerVersion.class,
ProcessCommandWrapperImpl.class,
RestartFlagHolderImpl.class,
ServerSettingsImpl.class,
- ServerImpl.class,
UuidFactoryImpl.INSTANCE,
+ UrlSettings.class,
EmbeddedDatabaseFactory.class,
DefaultDatabase.class,
DatabaseChecker.class,
@@ -119,6 +123,10 @@ public class PlatformLevel1 extends PlatformLevel {
org.sonar.core.properties.PropertiesDao.class);
addAll(CorePropertyDefinitions.all());
addAll(CePropertyDefinitions.all());
+
+ // cluster
+ addAll(ClusterProperties.definitions());
+ add(ClusterImpl.class);
}
private void addExtraRootComponents() {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
index e37fe5f20ee..eda34af591f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
@@ -26,11 +26,13 @@ import org.sonar.core.platform.PluginClassloaderFactory;
import org.sonar.core.platform.PluginLoader;
import org.sonar.db.charset.DatabaseCharsetChecker;
import org.sonar.db.version.MigrationStepModule;
+import org.sonar.server.platform.DefaultServerUpgradeStatus;
+import org.sonar.server.platform.ServerImpl;
+import org.sonar.server.platform.StartupMetadataProvider;
import org.sonar.server.platform.db.CheckDatabaseCharsetAtStartup;
import org.sonar.server.platform.db.migrations.DatabaseMigrator;
import org.sonar.server.platform.db.migrations.PlatformDatabaseMigration;
import org.sonar.server.platform.db.migrations.PlatformDatabaseMigrationExecutorServiceImpl;
-import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.web.RailsAppsDeployer;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.ServerPluginJarExploder;
@@ -47,10 +49,9 @@ public class PlatformLevel2 extends PlatformLevel {
@Override
protected void configureLevel() {
add(
+ new StartupMetadataProvider(),
+ ServerImpl.class,
DefaultServerUpgradeStatus.class,
- DatabaseMigrator.class,
- DatabaseCharsetChecker.class,
- CheckDatabaseCharsetAtStartup.class,
// depends on Ruby
PlatformRubyBridge.class,
@@ -73,6 +74,11 @@ public class PlatformLevel2 extends PlatformLevel {
// DB migration
PlatformDatabaseMigrationExecutorServiceImpl.class,
PlatformDatabaseMigration.class,
+ DatabaseMigrator.class,
MigrationStepModule.class);
+
+ addIfStartupLeader(
+ DatabaseCharsetChecker.class,
+ CheckDatabaseCharsetAtStartup.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
index 74c62e0d6d1..e465ae51be6 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
@@ -24,7 +24,7 @@ import org.sonar.core.util.DefaultHttpDownloader;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.ServerIdGenerator;
import org.sonar.server.platform.ServerIdLoader;
-import org.sonar.server.startup.ServerMetadataPersister;
+import org.sonar.server.platform.StartupMetadataPersister;
public class PlatformLevel3 extends PlatformLevel {
public PlatformLevel3(PlatformLevel parent) {
@@ -33,9 +33,9 @@ public class PlatformLevel3 extends PlatformLevel {
@Override
protected void configureLevel() {
+ addIfStartupLeader(StartupMetadataPersister.class);
add(
PersistentSettings.class,
- ServerMetadataPersister.class,
DefaultHttpDownloader.class,
UriReader.class,
ServerIdLoader.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 87004e66031..14dc8817611 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -305,6 +305,8 @@ public class PlatformLevel4 extends PlatformLevel {
@Override
protected void configureLevel() {
+ addIfStartupLeader(IndexCreator.class);
+
add(
PluginDownloader.class,
Views.class,
@@ -316,7 +318,6 @@ public class PlatformLevel4 extends PlatformLevel {
ServerWs.class,
BackendCleanup.class,
IndexDefinitions.class,
- IndexCreator.class,
// Activity
ActivityService.class,
@@ -680,7 +681,7 @@ public class PlatformLevel4 extends PlatformLevel {
@Override
public PlatformLevel start() {
- ServerExtensionInstaller extensionInstaller = getComponentByType(ServerExtensionInstaller.class);
+ ServerExtensionInstaller extensionInstaller = get(ServerExtensionInstaller.class);
extensionInstaller.installExtensions(getContainer());
super.start();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
index c072bab99f2..d4f714bc1be 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
@@ -20,10 +20,12 @@
package org.sonar.server.platform.platformlevel;
import org.sonar.server.app.ProcessCommandWrapper;
-import org.sonar.server.platform.db.CheckDatabaseCollationDuringMigration;
+import org.sonar.server.es.BaseIndexer;
import org.sonar.server.es.IndexerStartupTask;
import org.sonar.server.issue.filter.RegisterIssueFilters;
import org.sonar.server.platform.ServerLifecycleNotifier;
+import org.sonar.server.platform.db.CheckDatabaseCollationDuringMigration;
+import org.sonar.server.platform.web.RegisterServletFilters;
import org.sonar.server.qualitygate.RegisterQualityGates;
import org.sonar.server.qualityprofile.RegisterQualityProfiles;
import org.sonar.server.rule.RegisterRules;
@@ -36,7 +38,6 @@ import org.sonar.server.startup.RegisterDashboards;
import org.sonar.server.startup.RegisterMetrics;
import org.sonar.server.startup.RegisterNewMeasureFilters;
import org.sonar.server.startup.RegisterPermissionTemplates;
-import org.sonar.server.platform.web.RegisterServletFilters;
import org.sonar.server.startup.RenameDeprecatedPropertyKeys;
import org.sonar.server.startup.RenameIssueWidgets;
import org.sonar.server.user.DoPrivileged;
@@ -49,38 +50,40 @@ public class PlatformLevelStartup extends PlatformLevel {
@Override
protected void configureLevel() {
- add(
+ add(GeneratePluginIndex.class,
+ LogServerId.class,
+ RegisterServletFilters.class,
+ ServerLifecycleNotifier.class);
+
+ addIfStartupLeader(
CheckDatabaseCollationDuringMigration.class,
IndexerStartupTask.class,
RegisterMetrics.class,
RegisterQualityGates.class,
RegisterRules.class,
RegisterQualityProfiles.class,
- GeneratePluginIndex.class,
RegisterNewMeasureFilters.class,
RegisterDashboards.class,
RegisterPermissionTemplates.class,
RenameDeprecatedPropertyKeys.class,
- LogServerId.class,
- RegisterServletFilters.class,
RegisterIssueFilters.class,
RenameIssueWidgets.class,
- ServerLifecycleNotifier.class,
DisplayLogOnDeprecatedProjects.class,
ClearRulesOverloadedDebt.class,
- FeedUsersLocalStartupTask.class
- );
+ FeedUsersLocalStartupTask.class);
+
}
@Override
public PlatformLevel start() {
- DoPrivileged.execute(new DoPrivileged.Task(getComponentByType(ThreadLocalUserSession.class)) {
+ DoPrivileged.execute(new DoPrivileged.Task(get(ThreadLocalUserSession.class)) {
@Override
protected void doPrivileged() {
PlatformLevelStartup.super.start();
- getComponentByType(IndexerStartupTask.class).execute();
- getComponentByType(ServerLifecycleNotifier.class).notifyStart();
- getComponentByType(ProcessCommandWrapper.class).notifyOperational();
+ getOptional(IndexerStartupTask.class).ifPresent(IndexerStartupTask::execute);
+ get(ServerLifecycleNotifier.class).notifyStart();
+ get(ProcessCommandWrapper.class).notifyOperational();
+ getAll(BaseIndexer.class).forEach(i -> i.setEnabled(true));
}
});
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java
index 9de419f2bc0..c280730d666 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java
@@ -85,13 +85,13 @@ public class RailsAppsDeployer implements Startable {
if (hasRailsApp(pluginKey, appClassLoader)) {
LOG.info("Deploying app: " + pluginKey);
File appDir = new File(appsDir, pluginKey);
- ClassLoaderUtils.copyResources(appClassLoader, pathToRubyInitFile(pluginKey), appDir, relativePath -> {
+ ClassLoaderUtils.copyResources(appClassLoader, pathToRubyInitFile(pluginKey), appDir, relativePath ->
// Relocate the deployed files :
// relativePath format is: org/sonar/ror/sqale/app/controllers/foo_controller.rb
// app path is: org/sonar/ror/sqale
// -> deployed file is app/controllers/foo_controller.rb
- return StringUtils.substringAfter(relativePath, pluginKey + "/");
- });
+ StringUtils.substringAfter(relativePath, pluginKey + "/")
+ );
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/ServerMetadataPersister.java b/server/sonar-server/src/main/java/org/sonar/server/startup/ServerMetadataPersister.java
deleted file mode 100644
index fe0b4fcb8eb..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/startup/ServerMetadataPersister.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.startup;
-
-import com.google.common.collect.ImmutableMap;
-import org.picocontainer.Startable;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.platform.Server;
-import org.sonar.server.platform.PersistentSettings;
-
-import java.text.SimpleDateFormat;
-
-public final class ServerMetadataPersister implements Startable {
-
- private final Server server;
- private final PersistentSettings persistentSettings;
-
- public ServerMetadataPersister(Server server, PersistentSettings persistentSettings) {
- this.server = server;
- this.persistentSettings = persistentSettings;
- }
-
- @Override
- public void start() {
- Loggers.get(getClass()).debug("Persisting server metadata");
- persistentSettings.saveProperties(ImmutableMap.of(
- CoreProperties.SERVER_ID, server.getId(),
- CoreProperties.SERVER_VERSION, server.getVersion(),
- CoreProperties.SERVER_STARTTIME, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(server.getStartedAt())));
- }
-
- @Override
- public void stop() {
- // nothing
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java b/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java
index 68b5bb882a1..a5df8a85bb8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java
@@ -19,8 +19,6 @@
*/
package org.sonar.server.util;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import java.io.File;
@@ -29,6 +27,8 @@ import java.net.URL;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Enumeration;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
@@ -37,9 +37,6 @@ import org.apache.commons.lang.CharEncoding;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.utils.log.Loggers;
-/**
- * @since 3.0
- */
public class ClassLoaderUtils {
private ClassLoaderUtils() {
@@ -105,7 +102,7 @@ public class ClassLoaderUtils {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
- if (name.startsWith(rootDirectory) && predicate.apply(name)) {
+ if (name.startsWith(rootDirectory) && predicate.test(name)) {
paths.add(name);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java
index c19c7274ace..5eda287cefd 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java
@@ -19,29 +19,22 @@
*/
package org.sonar.server.platform;
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.function.Function;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang.StringUtils;
-import org.hamcrest.core.Is;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Collection;
import org.sonar.server.util.ClassLoaderUtils;
-import static org.junit.Assert.assertThat;
-import static org.junit.matchers.JUnitMatchers.hasItems;
+import static org.apache.commons.lang.StringUtils.endsWith;
+import static org.assertj.core.api.Assertions.assertThat;
public class ClassLoaderUtilsTest {
@@ -52,72 +45,61 @@ public class ClassLoaderUtilsTest {
@Before
public void prepareClassLoader() {
- // This JAR file has the three following files :
- // org/sonar/sqale/app/copyright.txt
- // org/sonar/sqale/app/README.md
- // org/sonar/other/other.txt
+ // This JAR file has the three following files :
+ // org/sonar/sqale/app/copyright.txt
+ // org/sonar/sqale/app/README.md
+ // org/sonar/other/other.txt
URL jarUrl = getClass().getResource("/org/sonar/server/platform/ClassLoaderUtilsTest/ClassLoaderUtilsTest.jar");
- classLoader = new URLClassLoader(new URL[]{jarUrl}, /* no parent classloader */null);
+ classLoader = new URLClassLoader(new URL[] {jarUrl}, /* no parent classloader */null);
}
@Test
public void listResources_unknown_root() {
- Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "unknown/directory", Predicates.<String>alwaysTrue());
- assertThat(strings.size(), Is.is(0));
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "unknown/directory", s -> true);
+ assertThat(strings).isEmpty();
}
@Test
public void listResources_all() {
- Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "org/sonar/sqale", Predicates.<String>alwaysTrue());
- assertThat(strings, hasItems(
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "org/sonar/sqale", s -> true);
+ assertThat(strings).containsOnly(
"org/sonar/sqale/",
"org/sonar/sqale/app/",
"org/sonar/sqale/app/copyright.txt",
- "org/sonar/sqale/app/README.md"));
- assertThat(strings.size(), Is.is(4));
+ "org/sonar/sqale/app/README.md");
}
@Test
public void listResources_use_predicate() {
- Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "org/sonar/sqale", new Predicate<String>() {
- public boolean apply(@Nullable String s) {
- return StringUtils.endsWith(s, "md");
- }
- });
- assertThat(strings.size(), Is.is(1));
- assertThat(strings, hasItems("org/sonar/sqale/app/README.md"));
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "org/sonar/sqale", s -> endsWith(s, "md"));
+ assertThat(strings).containsOnly("org/sonar/sqale/app/README.md");
}
@Test
public void listFiles() {
Collection<String> strings = ClassLoaderUtils.listFiles(classLoader, "org/sonar/sqale");
- assertThat(strings, hasItems(
+ assertThat(strings).containsOnly(
"org/sonar/sqale/app/copyright.txt",
- "org/sonar/sqale/app/README.md"));
- assertThat(strings.size(), Is.is(2));
+ "org/sonar/sqale/app/README.md");
}
@Test
public void copyRubyRailsApp() throws IOException {
File toDir = temp.newFolder("dest");
- ClassLoaderUtils.copyResources(classLoader, "org/sonar/sqale", toDir, Functions.<String>identity());
+ ClassLoaderUtils.copyResources(classLoader, "org/sonar/sqale", toDir, Function.identity());
- assertThat(FileUtils.listFiles(toDir, null, true).size(), Is.is(2));
- assertThat(new File(toDir, "org/sonar/sqale/app/copyright.txt").exists(), Is.is(true));
- assertThat(new File(toDir, "org/sonar/sqale/app/README.md").exists(), Is.is(true));
+ assertThat(FileUtils.listFiles(toDir, null, true)).hasSize(2);
+ assertThat(new File(toDir, "org/sonar/sqale/app/copyright.txt")).exists();
+ assertThat(new File(toDir, "org/sonar/sqale/app/README.md")).exists();
}
@Test
public void copyRubyRailsApp_relocate_files() throws IOException {
File toDir = temp.newFolder("dest");
- ClassLoaderUtils.copyResources(classLoader, "org/sonar/sqale", toDir, new Function<String, String>() {
- public String apply(@Nullable String path) {
- return "foo/" + FilenameUtils.getName(path);
- }
- });
+ ClassLoaderUtils.copyResources(classLoader, "org/sonar/sqale", toDir, path -> "foo/" + FilenameUtils.getName(path));
- assertThat(FileUtils.listFiles(toDir, null, true).size(), Is.is(2));
- assertThat(new File(toDir, "foo/copyright.txt").exists(), Is.is(true));
- assertThat(new File(toDir, "foo/README.md").exists(), Is.is(true));
+ assertThat(FileUtils.listFiles(toDir, null, true)).hasSize(2);
+ assertThat(new File(toDir, "foo/copyright.txt")).exists();
+ assertThat(new File(toDir, "foo/README.md")).exists();
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java
index b933afb0dea..4eefc54c7d8 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java
@@ -20,298 +20,73 @@
package org.sonar.server.platform;
import java.io.File;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Random;
-import org.hamcrest.core.Is;
-import org.junit.Before;
+import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarRuntime;
import org.sonar.api.config.Settings;
-import org.sonar.process.ProcessProperties;
+import org.sonar.api.utils.Version;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertThat;
-import static org.sonar.api.CoreProperties.SERVER_BASE_URL;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class ServerImplTest {
- private static final String HOST_PROPERTY = "sonar.web.host";
- private static final String PORT_PORPERTY = "sonar.web.port";
- private static final String CONTEXT_PROPERTY = "sonar.web.context";
+ private Settings settings = new Settings();
+ private StartupMetadata state = mock(StartupMetadata.class);
+ private ServerFileSystem fs = mock(ServerFileSystem.class);
+ private UrlSettings urlSettings = mock(UrlSettings.class);
+ private SonarRuntime runtime = mock(SonarRuntime.class);
+ private ServerImpl underTest = new ServerImpl(settings, state, fs, urlSettings, runtime);
@Rule
- public ExpectedException exception = ExpectedException.none();
- @Rule
- public TemporaryFolder sonarHome = new TemporaryFolder();
-
- private Date someDate;
- private Settings settings;
-
- ServerImpl underTest;
-
- @Before
- public void setUp() throws ParseException {
- this.someDate = new SimpleDateFormat("ddMMyyyy").parse("24101236");
- this.settings = new Settings().setProperty(ProcessProperties.PATH_HOME, sonarHome.getRoot().getAbsolutePath());
- this.settings.setProperty(ProcessProperties.STARTED_AT, someDate.getTime());
- new File(sonarHome.getRoot(), "web/deploy").mkdirs();
-
- underTest = new ServerImpl(settings, "/org/sonar/server/platform/ServerImplTest/build.properties", "/org/sonar/server/platform/ServerImplTest/version.txt");
- }
-
- @Test
- public void getStartedAt_throws_NPE_if_start_has_not_been_called() {
- exception.expect(NullPointerException.class);
- exception.expectMessage("start() method has not been called");
-
- underTest.getStartedAt();
- }
-
- @Test
- public void getStartedAt_is_date_from_sonar_core_startedAt() throws ParseException {
- underTest.start();
-
- assertThat(underTest.getStartedAt()).isEqualTo(someDate);
- }
-
- @Test
- public void start_fails_with_NFE_if_date_from_sonar_core_startedAt_is_invalid() throws ParseException {
- settings.setProperty(ProcessProperties.STARTED_AT, "aasasa");
-
- ServerImpl server = new ServerImpl(settings, "/org/sonar/server/platform/ServerImplTest/build.properties", "/org/sonar/server/platform/ServerImplTest/version.txt");
-
- exception.expect(NumberFormatException.class);
-
- server.start();
- }
-
- @Test
- public void start_fails_with_ISE_sonar_core_startedAt_is_not_set() throws ParseException {
- settings.removeProperty(ProcessProperties.STARTED_AT);
-
- ServerImpl server = new ServerImpl(settings, "/org/sonar/server/platform/ServerImplTest/build.properties", "/org/sonar/server/platform/ServerImplTest/version.txt");
-
- exception.expect(IllegalStateException.class);
- exception.expectMessage("property sonar.core.startedAt must be set");
-
- server.start();
- }
-
- @Test
- public void always_return_the_same_values() {
- underTest.start();
-
- assertThat(underTest.getId()).isNotNull();
- assertThat(underTest.getId()).isEqualTo(underTest.getId());
-
- assertThat(underTest.getVersion()).isNotNull();
- assertThat(underTest.getVersion()).isEqualTo(underTest.getVersion());
-
- assertThat(underTest.getStartedAt()).isNotNull();
- assertThat(underTest.getStartedAt()).isEqualTo(underTest.getStartedAt());
- }
-
- @Test
- public void read_version_from_file() {
- underTest.start();
-
- assertThat(underTest.getVersion()).isEqualTo("1.0");
- }
-
- @Test
- public void read_implementation_build_from_manifest() {
- underTest.start();
-
- assertThat(underTest.getImplementationBuild()).isEqualTo("0b9545a8b74aca473cb776275be4dc93a327c363");
- }
-
- @Test
- public void read_file_with_no_version() {
- exception.expect(IllegalStateException.class);
- exception.expectMessage("Unknown SonarQube version");
-
- ServerImpl server = new ServerImpl(settings, "", "/org/sonar/server/platform/ServerImplTest/empty-version.txt");
- server.start();
- }
-
- @Test
- public void read_file_with_empty_version() {
- exception.expect(IllegalStateException.class);
- exception.expectMessage("Unknown SonarQube version");
-
- ServerImpl server = new ServerImpl(settings, "", "/org/sonar/server/platform/ServerImplTest/empty-version.txt");
- server.start();
- }
-
- @Test
- public void fail_if_version_file_not_found() {
- exception.expect(IllegalStateException.class);
- exception.expectMessage("Unknown SonarQube version");
-
- ServerImpl server = new ServerImpl(settings, "", "/org/sonar/server/platform/ServerImplTest/unknown-file.properties");
- server.start();
- }
-
- @Test
- public void load_server_id_from_database() {
- Settings settings = new Settings();
- settings.setProperty(CoreProperties.PERMANENT_SERVER_ID, "abcde");
-
- ServerImpl server = new ServerImpl(settings);
-
- assertThat(server.getPermanentServerId(), Is.is("abcde"));
- }
+ public TemporaryFolder temp = new TemporaryFolder();
@Test
- public void use_default_context_path() {
- underTest.start();
- assertThat(underTest.getContextPath()).isEqualTo("");
- }
+ public void test_url_information() {
+ when(urlSettings.getContextPath()).thenReturn("/foo");
+ when(urlSettings.getBaseUrl()).thenReturn("http://localhost:9000/foo");
+ when(urlSettings.isDev()).thenReturn(true);
+ when(urlSettings.isSecured()).thenReturn(false);
- @Test
- public void is_dev() throws Exception {
- settings.setProperty("sonar.web.dev", true);
- underTest.start();
+ assertThat(underTest.getContextPath()).isEqualTo("/foo");
+ assertThat(underTest.getURL()).isEqualTo("http://localhost:9000/foo");
+ assertThat(underTest.getPublicRootUrl()).isEqualTo("http://localhost:9000/foo");
assertThat(underTest.isDev()).isTrue();
- }
-
- @Test
- public void get_default_public_root_url() throws Exception {
- underTest.start();
- assertThat(underTest.getPublicRootUrl()).isEqualTo("http://localhost:9000");
- }
-
- @Test
- public void get_public_root_url() throws Exception {
- settings.setProperty("sonar.core.serverBaseURL", "http://mydomain.com");
- underTest.start();
- assertThat(underTest.getPublicRootUrl()).isEqualTo("http://mydomain.com");
- }
-
- @Test
- public void is_secured_on_secured_server() throws Exception {
- settings.setProperty("sonar.core.serverBaseURL", "https://mydomain.com");
- underTest.start();
- assertThat(underTest.isSecured()).isTrue();
- }
-
- @Test
- public void is_secured_on_not_secured_server() throws Exception {
- settings.setProperty("sonar.core.serverBaseURL", "http://mydomain.com");
- underTest.start();
assertThat(underTest.isSecured()).isFalse();
}
@Test
- public void get_context_path_from_settings() {
- settings.setProperty(CONTEXT_PROPERTY, "/my_path");
- underTest.start();
- assertThat(underTest.getContextPath()).isEqualTo("/my_path");
- }
-
- @Test
- public void sanitize_context_path_from_settings() {
- settings.setProperty(CONTEXT_PROPERTY, "/my_path///");
- underTest.start();
- assertThat(underTest.getContextPath()).isEqualTo("/my_path");
- }
-
- @Test
- public void getUrl_returns_http_localhost_9000_when_not_settings_are_set() {
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
- }
+ public void test_file_system_information() throws IOException {
+ File home = temp.newFolder();
+ when(fs.getHomeDir()).thenReturn(home);
+ File deploy = temp.newFolder();
+ when(fs.getDeployDir()).thenReturn(deploy);
- @Test
- public void getUrl_returns_http_localhost_9000_when_serverBaseUrl_is_null() {
- settings.setProperty(SERVER_BASE_URL, (String) null);
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
+ assertThat(underTest.getDeployDir()).isEqualTo(deploy);
+ assertThat(underTest.getRootDir()).isEqualTo(home);
}
-
@Test
- public void getUrl_returns_serverBaseUrl_it_is_non_empty() {
- String serverBaseUrl = "whatever";
- settings.setProperty(SERVER_BASE_URL, serverBaseUrl);
- assertThat(underTest.getURL()).isEqualTo(serverBaseUrl);
- }
+ public void test_startup_information() throws IOException {
+ long time = 123_456_789L;
+ when(state.getStartedAt()).thenReturn(time);
+ when(state.getStartupId()).thenReturn("an_id");
- @Test
- public void getUrl_returns_http_localhost_9000_when_serverBaseUrl_is_empty() {
- settings.setProperty(SERVER_BASE_URL, "");
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
+ assertThat(underTest.getStartedAt().getTime()).isEqualTo(time);
+ assertThat(underTest.getId()).isEqualTo("an_id");
}
@Test
- public void getUrl_returns_http_localhost_9000_when_host_set_to_0_0_0_0() {
- settings.setProperty(HOST_PROPERTY, "0.0.0.0");
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
- }
+ public void test_runtime() throws IOException {
+ settings.setProperty(CoreProperties.PERMANENT_SERVER_ID, "an_id");
+ Version version = Version.create(6, 1);
+ when(runtime.getApiVersion()).thenReturn(version);
- @Test
- public void getUrl_returns_http_specified_host_9000_when_host_is_set() {
- settings.setProperty(HOST_PROPERTY, "my_host");
- assertThat(underTest.getURL()).isEqualTo("http://my_host:9000");
- }
-
- @Test
- public void getUrl_returns_http_localhost_specified_port_when_port_is_set() {
- settings.setProperty(PORT_PORPERTY, 951);
- assertThat(underTest.getURL()).isEqualTo("http://localhost:951");
- }
-
- @Test
- public void getUrl_returns_http_localhost_no_port_when_port_is_80() {
- settings.setProperty(PORT_PORPERTY, 80);
- assertThat(underTest.getURL()).isEqualTo("http://localhost");
- }
-
- @Test
- public void getUrl_returns_http_localhost_9000_when_port_is_0() {
- settings.setProperty(PORT_PORPERTY, 0);
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
- }
-
- @Test
- public void getUrl_returns_http_localhost_9000_when_port_is_less_then_0() {
- settings.setProperty(PORT_PORPERTY, -(Math.abs(new Random().nextInt(256))));
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
- }
-
- @Test
- public void getUrl_throws_NFE_when_port_not_an_int() {
- settings.setProperty(PORT_PORPERTY, "not a number");
-
- exception.expect(NumberFormatException.class);
-
- underTest.getURL();
- }
-
- @Test
- public void getUrl_returns_http_localhost_900_specified_context_when_context_is_set() {
- settings.setProperty(CONTEXT_PROPERTY, "sdsd");
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000sdsd");
- }
-
- @Test
- public void getUrl_returns_http_specified_host_no_port_when_host_is_set_and_port_is_80() {
- settings.setProperty(HOST_PROPERTY, "foo");
- settings.setProperty(PORT_PORPERTY, 80);
- assertThat(underTest.getURL()).isEqualTo("http://foo");
- }
-
- @Test
- public void getUrl_does_not_cache_returned_value() {
- assertThat(underTest.getURL()).isEqualTo("http://localhost:9000");
- settings.setProperty(HOST_PROPERTY, "foo");
- assertThat(underTest.getURL()).isEqualTo("http://foo:9000");
- settings.setProperty(PORT_PORPERTY, 666);
- assertThat(underTest.getURL()).isEqualTo("http://foo:666");
- settings.setProperty(CONTEXT_PROPERTY, "/bar");
- assertThat(underTest.getURL()).isEqualTo("http://foo:666/bar");
+ assertThat(underTest.getVersion()).isEqualTo(version.toString());
+ assertThat(underTest.getPermanentServerId()).isEqualTo("an_id");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java
new file mode 100644
index 00000000000..4fba9c60f5e
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class StartupMetadataPersisterTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+ private StartupMetadata metadata = new StartupMetadata("an_id", 123_456_789L);
+ private StartupMetadataPersister underTest = new StartupMetadataPersister(metadata, dbTester.getDbClient());
+
+ @Test
+ public void persist_metadata_at_startup() {
+ underTest.start();
+
+ assertPersistedProperty(CoreProperties.SERVER_ID, metadata.getStartupId());
+ assertPersistedProperty("sonar.core.startedAt", String.valueOf(metadata.getStartedAt()));
+
+ underTest.stop();
+ }
+
+ private void assertPersistedProperty(String propertyKey, String expectedValue) {
+ PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
+ assertThat(prop.getValue()).isEqualTo(expectedValue);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java
new file mode 100644
index 00000000000..4e5e7a6708a
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java
@@ -0,0 +1,137 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.Version;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.cluster.ClusterMock;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+
+public class StartupMetadataProviderTest {
+
+ private static final long A_DATE = 123_456_789L;
+ private static final String AN_ID = "generated_id";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+ private UuidFactory uuidFactory = mock(UuidFactory.class);
+ private StartupMetadataProvider underTest = new StartupMetadataProvider();
+ private System2 system = mock(System2.class);
+ private ClusterMock cluster = new ClusterMock();
+
+ @Test
+ public void generate_and_do_not_persist_metadata_if_server_is_startup_leader() {
+ when(uuidFactory.create()).thenReturn(AN_ID);
+ when(system.now()).thenReturn(A_DATE);
+ SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER);
+ cluster.setStartupLeader(true);
+
+ StartupMetadata metadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
+ assertThat(metadata.getStartupId()).isEqualTo(AN_ID);
+
+ assertNotPersistedProperty(CoreProperties.SERVER_ID);
+ assertNotPersistedProperty("sonar.core.startedAt");
+
+ // keep a cache
+ StartupMetadata secondMetadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ assertThat(secondMetadata).isSameAs(metadata);
+ }
+
+ @Test
+ public void load_from_database_if_server_is_startup_follower() {
+ SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER);
+ cluster.setStartupLeader(false);
+
+ testLoadingFromDatabase(runtime, false);
+ }
+
+ @Test
+ public void load_from_database_if_compute_engine_of_startup_leader_server() {
+ SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE);
+
+ testLoadingFromDatabase(runtime, true);
+ }
+
+ @Test
+ public void load_from_database_if_compute_engine_of_startup_follower_server() {
+ SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE);
+
+ testLoadingFromDatabase(runtime, false);
+ }
+
+ @Test
+ public void fail_to_load_from_database_if_properties_are_not_persisted() {
+ SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE);
+ cluster.setStartupLeader(false);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.core.id is missing in database");
+ underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ }
+
+ private void testLoadingFromDatabase(SonarRuntime runtime, boolean isStartupLeader) {
+ new StartupMetadataPersister(new StartupMetadata(AN_ID, A_DATE), dbTester.getDbClient()).start();
+ cluster.setStartupLeader(isStartupLeader);
+
+ StartupMetadata metadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
+ assertThat(metadata.getStartupId()).isEqualTo(AN_ID);
+
+ // still in database
+ assertPersistedProperty(CoreProperties.SERVER_ID, AN_ID);
+ assertPersistedProperty("sonar.core.startedAt", String.valueOf(A_DATE));
+
+ // keep a cache
+ StartupMetadata secondMetadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ assertThat(secondMetadata).isSameAs(metadata);
+
+ verifyZeroInteractions(uuidFactory, system);
+ }
+
+ private void assertPersistedProperty(String propertyKey, String expectedValue) {
+ PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
+ assertThat(prop.getValue()).isEqualTo(expectedValue);
+ }
+
+ private void assertNotPersistedProperty(String propertyKey) {
+ PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
+ assertThat(prop).isNull();
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/UrlSettingsTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/UrlSettingsTest.java
new file mode 100644
index 00000000000..0ef1d5c4163
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/UrlSettingsTest.java
@@ -0,0 +1,199 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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;
+
+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.core.config.CorePropertyDefinitions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.CoreProperties.SERVER_BASE_URL;
+
+public class UrlSettingsTest {
+
+ private static final String HOST_PROPERTY = "sonar.web.host";
+ private static final String PORT_PORPERTY = "sonar.web.port";
+ private static final String CONTEXT_PROPERTY = "sonar.web.context";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private Settings settings = new Settings(new PropertyDefinitions(CorePropertyDefinitions.all()));
+
+ @Test
+ public void use_default_context_path() {
+ assertThat(underTest().getContextPath()).isEqualTo("");
+ }
+
+ @Test
+ public void dev_mode_is_disabled_by_default() {
+ assertThat(underTest().isDev()).isFalse();
+ }
+
+ @Test
+ public void dev_mode_is_enabled() {
+ settings.setProperty("sonar.web.dev", true);
+
+ assertThat(underTest().isDev()).isTrue();
+ }
+
+ @Test
+ public void default_url() throws Exception {
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ }
+
+ @Test
+ public void base_url_is_configured() throws Exception {
+ settings.setProperty("sonar.core.serverBaseURL", "http://mydomain.com");
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://mydomain.com");
+ }
+
+ @Test
+ public void is_secured_on_https_server() throws Exception {
+ settings.setProperty("sonar.core.serverBaseURL", "https://mydomain.com");
+
+ assertThat(underTest().isSecured()).isTrue();
+ }
+
+ @Test
+ public void is_not_secured_if_http() throws Exception {
+ settings.setProperty("sonar.core.serverBaseURL", "http://mydomain.com");
+
+ assertThat(underTest().isSecured()).isFalse();
+ }
+
+ @Test
+ public void context_path_is_configured() {
+ settings.setProperty(CONTEXT_PROPERTY, "/my_path");
+
+ assertThat(underTest().getContextPath()).isEqualTo("/my_path");
+ }
+
+ @Test
+ public void sanitize_context_path_from_settings() {
+ settings.setProperty(CONTEXT_PROPERTY, "/my_path///");
+
+ assertThat(underTest().getContextPath()).isEqualTo("/my_path");
+ }
+
+ @Test
+ public void base_url_is_http_localhost_9000_when_serverBaseUrl_is_null() {
+ settings.setProperty(SERVER_BASE_URL, (String) null);
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ }
+
+ @Test
+ public void base_url_is_serverBaseUrl_if_non_empty() {
+ String serverBaseUrl = "whatever";
+ settings.setProperty(SERVER_BASE_URL, serverBaseUrl);
+
+ assertThat(underTest().getBaseUrl()).isEqualTo(serverBaseUrl);
+ }
+
+ @Test
+ public void base_url_is_http_localhost_9000_when_serverBaseUrl_is_empty() {
+ settings.setProperty(SERVER_BASE_URL, "");
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ }
+
+ @Test
+ public void base_url_is_http_localhost_9000_when_host_set_to_0_0_0_0() {
+ settings.setProperty(HOST_PROPERTY, "0.0.0.0");
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ }
+
+ @Test
+ public void base_url_is_http_specified_host_9000_when_host_is_set() {
+ settings.setProperty(HOST_PROPERTY, "my_host");
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://my_host:9000");
+ }
+
+ @Test
+ public void base_url_is_http_localhost_specified_port_when_port_is_set() {
+ settings.setProperty(PORT_PORPERTY, 951);
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:951");
+ }
+
+ @Test
+ public void base_url_is_http_localhost_no_port_when_port_is_80() {
+ settings.setProperty(PORT_PORPERTY, 80);
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost");
+ }
+
+ @Test
+ public void base_url_is_http_localhost_9000_when_port_is_0() {
+ settings.setProperty(PORT_PORPERTY, 0);
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ }
+
+ @Test
+ public void base_url_is_http_localhost_9000_when_port_is_negative() {
+ settings.setProperty(PORT_PORPERTY, -23);
+
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ }
+
+ @Test
+ public void getBaseUrl_throws_NFE_when_port_not_an_int() {
+ settings.setProperty(PORT_PORPERTY, "not a number");
+
+ expectedException.expect(NumberFormatException.class);
+ underTest().getBaseUrl();
+ }
+
+ @Test
+ public void base_url_is_http_localhost_900_specified_context_when_context_is_set() {
+ settings.setProperty(CONTEXT_PROPERTY, "sdsd");
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000sdsd");
+ }
+
+ @Test
+ public void base_url_is_http_specified_host_no_port_when_host_is_set_and_port_is_80() {
+ settings.setProperty(HOST_PROPERTY, "foo");
+ settings.setProperty(PORT_PORPERTY, 80);
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://foo");
+ }
+
+ @Test
+ public void getBaseUrl_does_not_cache_returned_value() {
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://localhost:9000");
+ settings.setProperty(HOST_PROPERTY, "foo");
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://foo:9000");
+ settings.setProperty(PORT_PORPERTY, 666);
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://foo:666");
+ settings.setProperty(CONTEXT_PROPERTY, "/bar");
+ assertThat(underTest().getBaseUrl()).isEqualTo("http://foo:666/bar");
+ }
+
+ private UrlSettings underTest() {
+ return new UrlSettings(settings);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java
new file mode 100644
index 00000000000..164f21f580b
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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 org.junit.Test;
+import org.sonar.test.TestUtils;
+
+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();
+ }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel1Test.java b/server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel1Test.java
new file mode 100644
index 00000000000..130e6051de2
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel1Test.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.platformlevel;
+
+import java.util.Properties;
+import org.junit.Test;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.utils.System2;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.cluster.Cluster;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+
+public class PlatformLevel1Test {
+
+ private PlatformLevel1 underTest = new PlatformLevel1(mock(Platform.class), new Properties());
+
+ @Test
+ public void no_missing_dependencies_between_components() {
+ underTest.configureLevel();
+
+ assertThat(underTest.getAll(PropertyDefinition.class)).isNotEmpty();
+ assertThat(underTest.getOptional(Cluster.class)).isPresent();
+ assertThat(underTest.getOptional(System2.class)).isPresent();
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java b/server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java
new file mode 100644
index 00000000000..31a242b0549
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.platformlevel;
+
+import java.util.Properties;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.System2;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.db.charset.DatabaseCharsetChecker;
+import org.sonar.process.ProcessProperties;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.cluster.Cluster;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class PlatformLevel2Test {
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private Properties props = new Properties();
+
+ @Before
+ public void setUp() throws Exception {
+ // these are mandatory settings declared by bootstrap process
+ props.setProperty(ProcessProperties.PATH_HOME, tempFolder.newFolder().getAbsolutePath());
+ props.setProperty(ProcessProperties.PATH_DATA, tempFolder.newFolder().getAbsolutePath());
+ props.setProperty(ProcessProperties.PATH_TEMP, tempFolder.newFolder().getAbsolutePath());
+ }
+
+ @Test
+ public void add_all_components_by_default() {
+ PlatformLevel1 level1 = new PlatformLevel1(mock(Platform.class), props);
+ level1.configureLevel();
+
+ PlatformLevel2 underTest = new PlatformLevel2(level1);
+ underTest.configureLevel();
+
+ // some level1 components
+ assertThat(underTest.getOptional(Cluster.class)).isPresent();
+ assertThat(underTest.getOptional(System2.class)).isPresent();
+
+ // level2 component that does not depend on cluster state
+ assertThat(underTest.getOptional(PluginRepository.class)).isPresent();
+
+ // level2 component that is injected only on "startup leaders"
+ assertThat(underTest.getOptional(DatabaseCharsetChecker.class)).isPresent();
+ }
+
+ @Test
+ public void do_not_add_all_components_when_startup_follower() {
+ props.setProperty("sonar.cluster.enabled", "true");
+ PlatformLevel1 level1 = new PlatformLevel1(mock(Platform.class), props);
+ level1.configureLevel();
+
+ PlatformLevel2 underTest = new PlatformLevel2(level1);
+ underTest.configureLevel();
+
+ assertThat(underTest.get(Cluster.class).isStartupLeader()).isFalse();
+
+ // level2 component that does not depend on cluster state
+ assertThat(underTest.getOptional(PluginRepository.class)).isPresent();
+
+ // level2 component that is injected only on "startup leaders"
+ assertThat(underTest.getOptional(DatabaseCharsetChecker.class)).isNotPresent();
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
index e21503eea16..9db727e3ba1 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
@@ -44,12 +44,12 @@ public class GeneratePluginIndexTest {
public TemporaryFolder temp = new TemporaryFolder();
private ServerFileSystem fileSystem = mock(ServerFileSystem.class);
- private File underTest;
+ private File index;
@Before
public void createIndexFile() {
- when(fileSystem.getPluginIndex()).thenReturn(underTest);
- underTest = new File("target/test-tmp/GeneratePluginIndexTest/plugins.txt");
+ index = new File("target/test-tmp/GeneratePluginIndexTest/plugins.txt");
+ when(fileSystem.getPluginIndex()).thenReturn(index);
}
@Test
@@ -61,7 +61,7 @@ public class GeneratePluginIndexTest {
new GeneratePluginIndex(fileSystem, repository).start();
- List<String> lines = FileUtils.readLines(underTest);
+ List<String> lines = FileUtils.readLines(index);
assertThat(lines.size(), Is.is(2));
assertThat(lines.get(0), containsString("sqale"));
assertThat(lines.get(1), containsString("checkstyle"));
diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/ServerMetadataPersisterTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/ServerMetadataPersisterTest.java
deleted file mode 100644
index 9b019612715..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/startup/ServerMetadataPersisterTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with 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.startup;
-
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.platform.Server;
-import org.sonar.server.platform.PersistentSettings;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Map;
-
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class ServerMetadataPersisterTest {
-
- PersistentSettings persistentSettings = mock(PersistentSettings.class);
-
- @Test
- public void testSaveProperties() throws ParseException {
- Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse("2010-05-18 17:59");
- Server server = mock(Server.class);
- when(server.getPermanentServerId()).thenReturn("1abcdef");
- when(server.getId()).thenReturn("123");
- when(server.getVersion()).thenReturn("3.2");
- when(server.getStartedAt()).thenReturn(date);
- ServerMetadataPersister persister = new ServerMetadataPersister(server, persistentSettings);
- persister.start();
-
- verify(persistentSettings).saveProperties(argThat(new ArgumentMatcher<Map<String, String>>() {
- @Override
- public boolean matches(Object argument) {
- Map<String, String> props = (Map<String, String>) argument;
- return props.get(CoreProperties.SERVER_ID).equals("123") &&
- props.get(CoreProperties.SERVER_VERSION).equals("3.2") &&
- // TODO complete with timestamp when test is fully isolated from JVM timezone
- props.get(CoreProperties.SERVER_STARTTIME).startsWith("2010-05-18T");
- }
- }));
- persister.stop();
- }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
index d194d1a6863..22130902b31 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
@@ -26,7 +26,6 @@ import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
-import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -103,7 +102,6 @@ public class ServerTester extends ExternalResource {
Properties properties = new Properties();
properties.putAll(initialProps);
esServerHolder = EsServerHolder.get();
- properties.setProperty(ProcessProperties.STARTED_AT, String.valueOf(new Date().getTime()));
properties.setProperty(ProcessProperties.CLUSTER_NAME, esServerHolder.getClusterName());
properties.setProperty(ProcessProperties.CLUSTER_NODE_NAME, esServerHolder.getNodeName());
properties.setProperty(ProcessProperties.SEARCH_PORT, String.valueOf(esServerHolder.getPort()));
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 8c8d73529a8..057386624ea 100644
--- a/sonar-application/src/main/java/org/sonar/application/App.java
+++ b/sonar-application/src/main/java/org/sonar/application/App.java
@@ -21,7 +21,6 @@ package org.sonar.application;
import java.io.File;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FilenameUtils;
@@ -57,7 +56,6 @@ public class App implements Stoppable {
private static List<JavaCommand> createCommands(Props props) {
File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
- props.set(ProcessProperties.STARTED_AT, String.valueOf(new Date().getTime()));
List<JavaCommand> commands = new ArrayList<>(3);
commands.add(createESCommand(props, homeDir));
diff --git a/sonar-application/src/test/java/org/sonar/application/AppTest.java b/sonar-application/src/test/java/org/sonar/application/AppTest.java
index 8150639c865..5a31d7d8e35 100644
--- a/sonar-application/src/test/java/org/sonar/application/AppTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/AppTest.java
@@ -80,22 +80,6 @@ public class AppTest {
}
@Test
- public void all_JavaCommand_have_a_sonar_core_startedAt_property_argument() throws IOException {
- Monitor monitor = mock(Monitor.class);
- App app = new App(monitor);
- Props props = initDefaultProps();
- app.start(props);
-
- ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor();
- verify(monitor).start(argument.capture());
-
- List<JavaCommand> javaCommands = argument.getValue();
- for (JavaCommand javaCommand : javaCommands) {
- assertThat(javaCommand.getArguments()).containsKey(ProcessProperties.STARTED_AT);
- }
- }
-
- @Test
public void add_custom_jdbc_driver_to_tomcat_classpath() throws Exception {
Monitor monitor = mock(Monitor.class);
App app = new App(monitor);
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java
index f8aa4bd2a2c..3c5c8e463e7 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java
@@ -39,6 +39,8 @@ public abstract class Server {
/**
* Name is misleading, this is an UUID generated
* at each startup, so it changes if server is restarted.
+ * In the context of cluster, the value is shared
+ * by all the nodes.
* @return a non-null UUID. Format can change over versions.
*/
public abstract String getId();
@@ -47,6 +49,7 @@ public abstract class Server {
* UUID generated on demand by system administrators. It is
* {@code null} by default on fresh installations. When defined,
* value does not change when server is restarted.
+ * In the context of cluster, value is the same on all nodes.
* @since 2.10
*/
@CheckForNull
@@ -58,7 +61,9 @@ public abstract class Server {
public abstract String getVersion();
/**
- * Date when server started
+ * Date when server started. In the context of cluster, this is the
+ * date of the startup of the first node. Value is the same on all
+ * cluster nodes.
*/
public abstract Date getStartedAt();
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java
index fc28bec5598..1862bfbfb23 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java
@@ -37,8 +37,8 @@ import static org.apache.commons.lang.StringUtils.trimToEmpty;
@ScannerSide
public class DefaultServer extends Server {
- private Settings settings;
- private BatchWsClient client;
+ private final Settings settings;
+ private final BatchWsClient client;
public DefaultServer(Settings settings, BatchWsClient client) {
this.settings = settings;