@@ -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, |
@@ -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, ""); | |||
} | |||
} | |||
} |
@@ -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 | |||
}; | |||
} | |||
@@ -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(); | |||
} | |||
} |
@@ -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"; |
@@ -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; |
@@ -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); | |||
} | |||
} | |||
} |
@@ -19,93 +19,36 @@ | |||
*/ | |||
package org.sonar.server.platform; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import com.google.common.base.Joiner; | |||
import com.google.common.io.Resources; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.net.URL; | |||
import java.nio.charset.StandardCharsets; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
import java.util.Properties; | |||
import javax.annotation.CheckForNull; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.picocontainer.Startable; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.SonarRuntime; | |||
import org.sonar.api.ce.ComputeEngineSide; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.platform.Server; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.api.server.ServerSide; | |||
import static com.google.common.base.Preconditions.checkNotNull; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static org.apache.commons.lang.StringUtils.isEmpty; | |||
import static org.apache.commons.lang.StringUtils.isNotEmpty; | |||
import static org.sonar.api.CoreProperties.SERVER_BASE_URL; | |||
import static org.sonar.server.app.TomcatContexts.PROPERTY_CONTEXT; | |||
public final class ServerImpl extends Server implements Startable { | |||
private static final String PROPERTY_SONAR_CORE_STARTED_AT = "sonar.core.startedAt"; | |||
private static final int DEFAULT_HTTP_PORT = 80; | |||
private static final String ALL_IPS_HOST = "0.0.0.0"; | |||
private static final Logger LOG = Loggers.get(ServerImpl.class); | |||
public static final int DEFAULT_PORT = 9000; | |||
@ComputeEngineSide | |||
@ServerSide | |||
public class ServerImpl extends Server { | |||
private final Settings settings; | |||
private final String buildProperties; | |||
private final String versionPath; | |||
private Date startedAt; | |||
private String id; | |||
private String version; | |||
private String implementationBuild; | |||
private File sonarHome; | |||
private String contextPath; | |||
public ServerImpl(Settings settings) { | |||
this(settings, "/build.properties", "/sq-version.txt"); | |||
} | |||
private final StartupMetadata state; | |||
private final ServerFileSystem fs; | |||
private final UrlSettings urlSettings; | |||
private final SonarRuntime runtime; | |||
@VisibleForTesting | |||
ServerImpl(Settings settings, String buildProperties, String versionPath) { | |||
public ServerImpl(Settings settings, StartupMetadata state, ServerFileSystem fs, UrlSettings urlSettings, SonarRuntime runtime) { | |||
this.settings = settings; | |||
this.buildProperties = buildProperties; | |||
this.versionPath = versionPath; | |||
} | |||
@Override | |||
public void start() { | |||
try { | |||
String startedAtString = settings.getString(PROPERTY_SONAR_CORE_STARTED_AT); | |||
checkState(startedAtString != null, "property %s must be set", PROPERTY_SONAR_CORE_STARTED_AT); | |||
startedAt = new Date(Long.valueOf(startedAtString)); | |||
id = new SimpleDateFormat("yyyyMMddHHmmss").format(startedAt); | |||
version = readVersion(versionPath); | |||
implementationBuild = read(buildProperties).getProperty("Implementation-Build"); | |||
sonarHome = new File(settings.getString(ProcessProperties.PATH_HOME)); | |||
if (!sonarHome.isDirectory()) { | |||
throw new IllegalStateException("SonarQube home directory is not valid"); | |||
} | |||
contextPath = StringUtils.defaultIfBlank(settings.getString(PROPERTY_CONTEXT), "") | |||
// Remove trailing slashes | |||
.replaceFirst("(\\/+)$", ""); | |||
LOG.info("SonarQube {}", Joiner.on(" / ").skipNulls().join("Server", version, implementationBuild)); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Can not load metadata", e); | |||
} | |||
this.state = state; | |||
this.fs = fs; | |||
this.urlSettings = urlSettings; | |||
this.runtime = runtime; | |||
} | |||
@Override | |||
public void stop() { | |||
// do nothing | |||
public String getId() { | |||
return state.getStartupId(); | |||
} | |||
@Override | |||
@@ -113,126 +56,49 @@ public final class ServerImpl extends Server implements Startable { | |||
return settings.getString(CoreProperties.PERMANENT_SERVER_ID); | |||
} | |||
@Override | |||
public String getId() { | |||
return id; | |||
} | |||
@Override | |||
public String getVersion() { | |||
return version; | |||
} | |||
public String getImplementationBuild() { | |||
return implementationBuild; | |||
return runtime.getApiVersion().toString(); | |||
} | |||
@Override | |||
public Date getStartedAt() { | |||
return checkNotNull(startedAt, "start() method has not been called"); | |||
return new Date(state.getStartedAt()); | |||
} | |||
@Override | |||
public File getRootDir() { | |||
return sonarHome; | |||
return fs.getHomeDir(); | |||
} | |||
@Override | |||
@CheckForNull | |||
public File getDeployDir() { | |||
return null; | |||
return fs.getDeployDir(); | |||
} | |||
@Override | |||
public String getContextPath() { | |||
return contextPath; | |||
return urlSettings.getContextPath(); | |||
} | |||
@Override | |||
public String getPublicRootUrl() { | |||
return getURL(); | |||
return urlSettings.getBaseUrl(); | |||
} | |||
@Override | |||
public boolean isDev() { | |||
return settings.getBoolean("sonar.web.dev"); | |||
return urlSettings.isDev(); | |||
} | |||
@Override | |||
public boolean isSecured() { | |||
return getURL().startsWith("https://"); | |||
} | |||
private static String readVersion(String filename) throws IOException { | |||
URL url = ServerImpl.class.getResource(filename); | |||
if (url != null) { | |||
String version = Resources.toString(url, StandardCharsets.UTF_8); | |||
if (!StringUtils.isBlank(version)) { | |||
return StringUtils.deleteWhitespace(version); | |||
} | |||
} | |||
throw new IllegalStateException("Unknown SonarQube version"); | |||
} | |||
private static Properties read(String filename) throws IOException { | |||
Properties properties = new Properties(); | |||
InputStream stream = null; | |||
try { | |||
stream = ServerImpl.class.getResourceAsStream(filename); | |||
if (stream != null) { | |||
properties.load(stream); | |||
} | |||
} finally { | |||
IOUtils.closeQuietly(stream); | |||
} | |||
return properties; | |||
return urlSettings.isSecured(); | |||
} | |||
@Override | |||
public String getURL() { | |||
String serverBaseUrl = settings.getString(SERVER_BASE_URL); | |||
if (isEmpty(serverBaseUrl)) { | |||
return computeUrl(); | |||
} | |||
return serverBaseUrl; | |||
} | |||
private String computeUrl() { | |||
String host = settings.getString("sonar.web.host"); | |||
int port = settings.getInt("sonar.web.port"); | |||
String context = settings.getString("sonar.web.context"); | |||
StringBuilder res = new StringBuilder(); | |||
res.append("http://"); | |||
appendHost(host, res); | |||
appendPort(port, res); | |||
appendContext(context, res); | |||
return res.toString(); | |||
} | |||
private static void appendHost(String host, StringBuilder res) { | |||
if (isEmpty(host) || ALL_IPS_HOST.equals(host)) { | |||
res.append("localhost"); | |||
} else { | |||
res.append(host); | |||
} | |||
} | |||
private static void appendPort(int port, StringBuilder res) { | |||
if (port < 1) { | |||
res.append(':').append(DEFAULT_PORT); | |||
} else if (port != DEFAULT_HTTP_PORT) { | |||
res.append(':').append(port); | |||
} | |||
} | |||
private static void appendContext(String context, StringBuilder res) { | |||
if (isNotEmpty(context)) { | |||
res.append(context); | |||
} | |||
return urlSettings.getBaseUrl(); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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) |
@@ -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()])); | |||
} |
@@ -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() { |
@@ -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); | |||
} | |||
} |
@@ -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, |
@@ -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(); |
@@ -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)); | |||
} | |||
}); | |||
@@ -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 + "/") | |||
); | |||
} | |||
} | |||
@@ -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 | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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")); |
@@ -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(); | |||
} | |||
} |
@@ -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())); |
@@ -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)); | |||
@@ -79,22 +79,6 @@ public class AppTest { | |||
assertThat(argument.getValue()).extracting("processId").containsOnly(ProcessId.ELASTICSEARCH); | |||
} | |||
@Test | |||
public void all_JavaCommand_have_a_sonar_core_startedAt_property_argument() throws IOException { | |||
Monitor monitor = mock(Monitor.class); | |||
App app = new App(monitor); | |||
Props props = initDefaultProps(); | |||
app.start(props); | |||
ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor(); | |||
verify(monitor).start(argument.capture()); | |||
List<JavaCommand> javaCommands = argument.getValue(); | |||
for (JavaCommand javaCommand : javaCommands) { | |||
assertThat(javaCommand.getArguments()).containsKey(ProcessProperties.STARTED_AT); | |||
} | |||
} | |||
@Test | |||
public void add_custom_jdbc_driver_to_tomcat_classpath() throws Exception { | |||
Monitor monitor = mock(Monitor.class); |
@@ -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(); | |||
@@ -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; |