Browse Source

SONAR-7899 configure container of "startup followers"

tags/6.1-RC1
Simon Brandhof 7 years ago
parent
commit
f0b34e1199
38 changed files with 1267 additions and 678 deletions
  1. 2
    0
      it/it-tests/src/test/java/it/Category5Suite.java
  2. 125
    0
      it/it-tests/src/test/java/it/serverSystem/ClusterTest.java
  3. 10
    6
      server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
  4. 12
    5
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  5. 0
    2
      server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
  6. 0
    2
      server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java
  7. 64
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/LogServerVersion.java
  8. 26
    160
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
  9. 4
    4
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java
  10. 48
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadata.java
  11. 61
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java
  12. 84
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataProvider.java
  13. 106
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/UrlSettings.java
  14. 4
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java
  15. 30
    6
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java
  16. 12
    4
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
  17. 10
    4
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
  18. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
  19. 3
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  20. 16
    13
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
  21. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java
  22. 0
    54
      server/sonar-server/src/main/java/org/sonar/server/startup/ServerMetadataPersister.java
  23. 3
    6
      server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java
  24. 30
    48
      server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java
  25. 39
    264
      server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java
  26. 57
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java
  27. 137
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java
  28. 199
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/UrlSettingsTest.java
  29. 35
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java
  30. 45
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel1Test.java
  31. 88
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java
  32. 4
    4
      server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
  33. 0
    66
      server/sonar-server/src/test/java/org/sonar/server/startup/ServerMetadataPersisterTest.java
  34. 0
    2
      server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
  35. 0
    2
      sonar-application/src/main/java/org/sonar/application/App.java
  36. 0
    16
      sonar-application/src/test/java/org/sonar/application/AppTest.java
  37. 6
    1
      sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java
  38. 2
    2
      sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java

+ 2
- 0
it/it-tests/src/test/java/it/Category5Suite.java View File

@@ -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,

+ 125
- 0
it/it-tests/src/test/java/it/serverSystem/ClusterTest.java View File

@@ -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, "");
}
}
}

+ 10
- 6
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java View File

@@ -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
};
}


+ 12
- 5
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

@@ -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();
}
}

+ 0
- 2
server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java View File

@@ -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";

+ 0
- 2
server/sonar-server/src/main/java/org/sonar/server/es/BaseIndexer.java View File

@@ -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;

+ 64
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/LogServerVersion.java View File

@@ -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);
}
}
}

+ 26
- 160
server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java View File

@@ -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
@@ -113,126 +56,49 @@ public final class ServerImpl extends Server implements Startable {
return settings.getString(CoreProperties.PERMANENT_SERVER_ID);
}

@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();
}

}

+ 4
- 4
server/sonar-server/src/main/java/org/sonar/server/platform/ServerSettingsImpl.java View File

@@ -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;
}

+ 48
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadata.java View File

@@ -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;
}
}

+ 61
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataPersister.java View File

@@ -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
}
}

+ 84
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/StartupMetadataProvider.java View File

@@ -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();
}
}

+ 106
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/UrlSettings.java View File

@@ -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);
}
}
}

+ 4
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java View File

@@ -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)

+ 30
- 6
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java View File

@@ -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()]));
}

+ 12
- 4
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java View File

@@ -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() {

+ 10
- 4
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java View File

@@ -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);
}
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java View File

@@ -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,

+ 3
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -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();

+ 16
- 13
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java View File

@@ -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));
}
});


+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java View File

@@ -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 + "/")
);
}
}


+ 0
- 54
server/sonar-server/src/main/java/org/sonar/server/startup/ServerMetadataPersister.java View File

@@ -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
}
}

+ 3
- 6
server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java View File

@@ -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);
}
}

+ 30
- 48
server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java View File

@@ -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();
}
}

+ 39
- 264
server/sonar-server/src/test/java/org/sonar/server/platform/ServerImplTest.java View File

@@ -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");
}
}

+ 57
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java View File

@@ -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);
}
}

+ 137
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java View File

@@ -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();
}
}

+ 199
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/UrlSettingsTest.java View File

@@ -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);
}
}

+ 35
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterPropertiesTest.java View File

@@ -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();
}

}

+ 45
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel1Test.java View File

@@ -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();
}
}

+ 88
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java View File

@@ -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();
}
}

+ 4
- 4
server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java View File

@@ -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"));

+ 0
- 66
server/sonar-server/src/test/java/org/sonar/server/startup/ServerMetadataPersisterTest.java View File

@@ -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();
}

}

+ 0
- 2
server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java View File

@@ -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()));

+ 0
- 2
sonar-application/src/main/java/org/sonar/application/App.java View File

@@ -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));


+ 0
- 16
sonar-application/src/test/java/org/sonar/application/AppTest.java View File

@@ -79,22 +79,6 @@ public class AppTest {
assertThat(argument.getValue()).extracting("processId").containsOnly(ProcessId.ELASTICSEARCH);
}

@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);

+ 6
- 1
sonar-plugin-api/src/main/java/org/sonar/api/platform/Server.java View File

@@ -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();


+ 2
- 2
sonar-scanner-engine/src/main/java/org/sonar/scanner/platform/DefaultServer.java View File

@@ -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;

Loading…
Cancel
Save