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