@@ -24,7 +24,6 @@ import java.io.IOException; | |||
import java.util.Date; | |||
import java.util.Properties; | |||
import java.util.stream.Collectors; | |||
import org.apache.commons.dbcp.BasicDataSource; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -42,6 +41,8 @@ import org.sonar.db.property.PropertyDto; | |||
import org.sonar.process.ProcessId; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import org.sonar.server.platform.ServerIdChecksum; | |||
import org.sonar.server.property.InternalProperties; | |||
import static java.lang.String.valueOf; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
@@ -59,7 +60,7 @@ public class ComputeEngineContainerImplTest { | |||
@Rule | |||
public TemporaryFolder tempFolder = new TemporaryFolder(); | |||
@Rule | |||
public DbTester dbTester = DbTester.create(System2.INSTANCE); | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
private ComputeEngineContainerImpl underTest; | |||
@@ -79,8 +80,9 @@ public class ComputeEngineContainerImplTest { | |||
Properties properties = getProperties(); | |||
// required persisted properties | |||
insertProperty(CoreProperties.SERVER_ID, "a_startup_id"); | |||
insertProperty(CoreProperties.SERVER_ID, "a_server_id"); | |||
insertProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(new Date())); | |||
insertInternalProperty(InternalProperties.SERVER_ID_CHECKSUM, ServerIdChecksum.of("a_server_id", db.getUrl())); | |||
underTest | |||
.start(new Props(properties)); | |||
@@ -141,7 +143,7 @@ public class ComputeEngineContainerImplTest { | |||
properties.setProperty(PATH_TEMP, tmpDir.getAbsolutePath()); | |||
properties.setProperty(PROPERTY_PROCESS_INDEX, valueOf(ProcessId.COMPUTE_ENGINE.getIpcIndex())); | |||
properties.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath()); | |||
properties.setProperty(DatabaseProperties.PROP_URL, ((BasicDataSource) dbTester.database().getDataSource()).getUrl()); | |||
properties.setProperty(DatabaseProperties.PROP_URL, db.getUrl()); | |||
properties.setProperty(DatabaseProperties.PROP_USER, "sonar"); | |||
properties.setProperty(DatabaseProperties.PROP_PASSWORD, "sonar"); | |||
return properties; | |||
@@ -149,7 +151,12 @@ public class ComputeEngineContainerImplTest { | |||
private void insertProperty(String key, String value) { | |||
PropertyDto dto = new PropertyDto().setKey(key).setValue(value); | |||
dbTester.getDbClient().propertiesDao().saveProperty(dbTester.getSession(), dto); | |||
dbTester.commit(); | |||
db.getDbClient().propertiesDao().saveProperty(db.getSession(), dto); | |||
db.commit(); | |||
} | |||
private void insertInternalProperty(String key, String value) { | |||
db.getDbClient().internalPropertiesDao().save(db.getSession(), key, value); | |||
db.commit(); | |||
} | |||
} |
@@ -24,6 +24,7 @@ import java.sql.SQLException; | |||
import java.util.List; | |||
import java.util.Map; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.dbcp.BasicDataSource; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.picocontainer.containers.TransientPicoContainer; | |||
import org.sonar.api.utils.System2; | |||
@@ -300,6 +301,10 @@ public class DbTester extends AbstractDbTester<TestDb> { | |||
return db.getDatabase(); | |||
} | |||
public String getUrl() { | |||
return ((BasicDataSource) db.getDatabase().getDataSource()).getUrl(); | |||
} | |||
public DatabaseCommands getCommands() { | |||
return db.getCommands(); | |||
} |
@@ -228,8 +228,9 @@ public class BackendCleanup { | |||
* Internal property {@link InternalProperties#DEFAULT_ORGANIZATION} must never be deleted. | |||
*/ | |||
private static void truncateInternalProperties(String tableName, Statement ddlStatement, Connection connection) throws SQLException { | |||
try (PreparedStatement preparedStatement = connection.prepareStatement("delete from internal_properties where kee <> ?")) { | |||
try (PreparedStatement preparedStatement = connection.prepareStatement("delete from internal_properties where kee not in (?,?)")) { | |||
preparedStatement.setString(1, InternalProperties.DEFAULT_ORGANIZATION); | |||
preparedStatement.setString(2, InternalProperties.SERVER_ID_CHECKSUM); | |||
preparedStatement.execute(); | |||
// commit is useless on some databases | |||
connection.commit(); |
@@ -0,0 +1,99 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.platform; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import java.util.Arrays; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.utils.KeyValueFormat; | |||
public class ServerIdChecksum { | |||
private static final String SQLSERVER_PREFIX = "jdbc:sqlserver://"; | |||
private ServerIdChecksum() { | |||
// only static methods | |||
} | |||
public static String of(String serverId, String jdbcUrl) { | |||
return DigestUtils.sha256Hex(serverId + "|" + sanitizeJdbcUrl(jdbcUrl)); | |||
} | |||
@VisibleForTesting | |||
static String sanitizeJdbcUrl(String jdbcUrl) { | |||
String result; | |||
if (jdbcUrl.startsWith(SQLSERVER_PREFIX)) { | |||
result = sanitizeSqlServerUrl(jdbcUrl); | |||
} else { | |||
// remove query parameters, they don't aim to reference the schema | |||
result = StringUtils.substringBefore(jdbcUrl, "?"); | |||
} | |||
return StringUtils.lowerCase(result, Locale.ENGLISH); | |||
} | |||
/** | |||
* Deal with this strange URL format: | |||
* https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url | |||
* https://docs.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties | |||
*/ | |||
private static String sanitizeSqlServerUrl(String jdbcUrl) { | |||
StringBuilder result = new StringBuilder(); | |||
result.append(SQLSERVER_PREFIX); | |||
String host; | |||
if (jdbcUrl.contains(";")) { | |||
host = StringUtils.substringBetween(jdbcUrl, SQLSERVER_PREFIX, ";"); | |||
} else { | |||
host = StringUtils.substringAfter(jdbcUrl, SQLSERVER_PREFIX); | |||
} | |||
String queryString = StringUtils.substringAfter(jdbcUrl, ";"); | |||
Map<String, String> parameters = KeyValueFormat.parse(queryString); | |||
Optional<String> server = firstValue(parameters, "serverName", "servername", "server"); | |||
if (server.isPresent()) { | |||
result.append(server.get()); | |||
} else { | |||
result.append(StringUtils.substringBefore(host, ":")); | |||
} | |||
Optional<String> port = firstValue(parameters, "portNumber", "port"); | |||
if (port.isPresent()) { | |||
result.append(':').append(port.get()); | |||
} else if (host.contains(":")) { | |||
result.append(':').append(StringUtils.substringAfter(host, ":")); | |||
} | |||
Optional<String> database = firstValue(parameters, "databaseName", "database"); | |||
database.ifPresent(s -> result.append('/').append(s)); | |||
return result.toString(); | |||
} | |||
private static Optional<String> firstValue(Map<String, String> map, String... keys) { | |||
return Arrays.stream(keys) | |||
.map(map::get) | |||
.filter(Objects::nonNull) | |||
.findFirst(); | |||
} | |||
} |
@@ -21,27 +21,37 @@ package org.sonar.server.platform; | |||
import java.text.ParseException; | |||
import java.text.SimpleDateFormat; | |||
import javax.annotation.Nullable; | |||
import java.util.Optional; | |||
import org.picocontainer.Startable; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.SonarQubeSide; | |||
import org.sonar.api.SonarRuntime; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
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.property.InternalProperties; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static org.apache.commons.lang.StringUtils.isBlank; | |||
import static org.apache.commons.lang.StringUtils.isEmpty; | |||
import static org.apache.commons.lang.StringUtils.isNotEmpty; | |||
import static org.sonar.api.CoreProperties.SERVER_ID; | |||
import static org.sonar.server.property.InternalProperties.SERVER_ID_CHECKSUM; | |||
public class ServerIdManager implements Startable { | |||
private static final Logger LOGGER = Loggers.get(ServerIdManager.class); | |||
private final Configuration config; | |||
private final DbClient dbClient; | |||
private final SonarRuntime runtime; | |||
private final WebServer webServer; | |||
private final UuidFactory uuidFactory; | |||
public ServerIdManager(DbClient dbClient, SonarRuntime runtime, WebServer webServer, UuidFactory uuidFactory) { | |||
public ServerIdManager(Configuration config, DbClient dbClient, SonarRuntime runtime, WebServer webServer, UuidFactory uuidFactory) { | |||
this.config = config; | |||
this.dbClient = dbClient; | |||
this.runtime = runtime; | |||
this.webServer = webServer; | |||
@@ -51,24 +61,62 @@ public class ServerIdManager implements Startable { | |||
@Override | |||
public void start() { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
PropertyDto dto = dbClient.propertiesDao().selectGlobalProperty(dbSession, SERVER_ID); | |||
if (runtime.getSonarQubeSide() == SonarQubeSide.SERVER && webServer.isStartupLeader()) { | |||
persistServerIdIfMissingOrOldFormatted(dbSession, dto); | |||
if (needsToBeDropped(dbSession)) { | |||
dbClient.propertiesDao().deleteGlobalProperty(SERVER_ID, dbSession); | |||
} | |||
persistServerIdIfMissing(dbSession); | |||
dbSession.commit(); | |||
} else { | |||
ensureServerIdIsSet(dto); | |||
ensureServerIdIsValid(dbSession); | |||
} | |||
} | |||
} | |||
/** | |||
* Insert or update {@link CoreProperties#SERVER_ID} property in DB to a UUID if it doesn't exist or if it's a date | |||
* (per the old format of {@link CoreProperties#SERVER_ID} before 6.1). | |||
*/ | |||
private void persistServerIdIfMissingOrOldFormatted(DbSession dbSession, @Nullable PropertyDto dto) { | |||
if (dto == null || dto.getValue().isEmpty() || isDate(dto.getValue())) { | |||
dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(SERVER_ID).setValue(uuidFactory.create())); | |||
dbSession.commit(); | |||
private boolean needsToBeDropped(DbSession dbSession) { | |||
PropertyDto dto = dbClient.propertiesDao().selectGlobalProperty(dbSession, SERVER_ID); | |||
if (dto == null) { | |||
// does not exist, no need to drop | |||
return false; | |||
} | |||
if (isEmpty(dto.getValue())) { | |||
return true; | |||
} | |||
if (isDate(dto.getValue())) { | |||
LOGGER.info("Server ID is changed to new format."); | |||
return true; | |||
} | |||
Optional<String> checksum = dbClient.internalPropertiesDao().selectByKey(dbSession, SERVER_ID_CHECKSUM); | |||
if (checksum.isPresent()) { | |||
String expectedChecksum = computeChecksum(dto.getValue()); | |||
if (!expectedChecksum.equals(checksum.get())) { | |||
LOGGER.warn("Server ID is reset because it is not valid anymore. Database URL probably changed. The new server ID affects SonarSource licensed products."); | |||
return true; | |||
} | |||
} | |||
// Existing server ID must be kept when upgrading to 6.7+. In that case the checksum does | |||
// not exist. | |||
return false; | |||
} | |||
private void persistServerIdIfMissing(DbSession dbSession) { | |||
String serverId; | |||
PropertyDto idDto = dbClient.propertiesDao().selectGlobalProperty(dbSession, SERVER_ID); | |||
if (idDto == null) { | |||
serverId = uuidFactory.create(); | |||
dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(SERVER_ID).setValue(serverId)); | |||
} else { | |||
serverId = idDto.getValue(); | |||
} | |||
// checksum must be generated when it does not exist (upgrading to 6.7 or greater) | |||
// or when server ID changed. | |||
dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, computeChecksum(serverId)); | |||
} | |||
/** | |||
@@ -83,9 +131,19 @@ public class ServerIdManager implements Startable { | |||
} | |||
} | |||
private static void ensureServerIdIsSet(@Nullable PropertyDto dto) { | |||
checkState(dto != null, "Property %s is missing in database", SERVER_ID); | |||
checkState(!isBlank(dto.getValue()), "Property %s is set but empty in database", SERVER_ID); | |||
private String computeChecksum(String serverId) { | |||
String jdbcUrl = config.get("sonar.jdbc.url").orElseThrow(() -> new IllegalStateException("Missing JDBC URL")); | |||
return ServerIdChecksum.of(serverId, jdbcUrl); | |||
} | |||
private void ensureServerIdIsValid(DbSession dbSession) { | |||
PropertyDto id = dbClient.propertiesDao().selectGlobalProperty(dbSession, SERVER_ID); | |||
checkState(id != null, "Property %s is missing in database", SERVER_ID); | |||
checkState(isNotEmpty(id.getValue()), "Property %s is empty in database", SERVER_ID); | |||
Optional<String> checksum = dbClient.internalPropertiesDao().selectByKey(dbSession, SERVER_ID_CHECKSUM); | |||
checkState(checksum.isPresent(), "Internal property %s is missing in database", SERVER_ID_CHECKSUM); | |||
checkState(checksum.get().equals(computeChecksum(id.getValue())), "Server ID is invalid"); | |||
} | |||
@Override |
@@ -34,6 +34,7 @@ public interface InternalProperties { | |||
String ORGANIZATION_ENABLED = "organization.enabled"; | |||
String SERVER_ID_CHECKSUM = "server.idChecksum"; | |||
/** | |||
* Read the value of the specified property. | |||
* |
@@ -0,0 +1,95 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.platform; | |||
import org.junit.Test; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class ServerIdChecksumTest { | |||
@Test | |||
public void test_checksum() { | |||
assertThat(ServerIdChecksum.of("id1", "url1")) | |||
.isNotEmpty() | |||
.isEqualTo(ServerIdChecksum.of("id1", "url1")) | |||
.isNotEqualTo(ServerIdChecksum.of("id1", "url2")) | |||
.isNotEqualTo(ServerIdChecksum.of("id2", "url1")) | |||
.isNotEqualTo(ServerIdChecksum.of("id2", "url2")); | |||
} | |||
@Test | |||
public void sanitize_h2_url() { | |||
verifyJdbcUrl("jdbc:h2:tcp://dbserv:8084/~/sample", "jdbc:h2:tcp://dbserv:8084/~/sample"); | |||
verifyJdbcUrl("jdbc:h2:tcp://localhost/mem:test", "jdbc:h2:tcp://localhost/mem:test"); | |||
verifyJdbcUrl("jdbc:h2:tcp://localhost/mem:TEST", "jdbc:h2:tcp://localhost/mem:test"); | |||
} | |||
@Test | |||
public void sanitize_mysql_url() { | |||
verifyJdbcUrl("jdbc:mysql://127.0.0.1:3306/sonarqube?useUnicode=true&characterEncoding=utf8", "jdbc:mysql://127.0.0.1:3306/sonarqube"); | |||
verifyJdbcUrl("jdbc:mysql://127.0.0.1:3306/sonarqube", "jdbc:mysql://127.0.0.1:3306/sonarqube"); | |||
verifyJdbcUrl("jdbc:mysql://127.0.0.1:3306/SONARQUBE", "jdbc:mysql://127.0.0.1:3306/sonarqube"); | |||
} | |||
@Test | |||
public void sanitize_oracle_url() { | |||
verifyJdbcUrl("sonar.jdbc.url=jdbc:oracle:thin:@localhost:1521/XE", "sonar.jdbc.url=jdbc:oracle:thin:@localhost:1521/xe"); | |||
verifyJdbcUrl("sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE", "sonar.jdbc.url=jdbc:oracle:thin:@localhost/xe"); | |||
verifyJdbcUrl("sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE?foo", "sonar.jdbc.url=jdbc:oracle:thin:@localhost/xe"); | |||
verifyJdbcUrl("sonar.jdbc.url=jdbc:oracle:thin:@LOCALHOST/XE?foo", "sonar.jdbc.url=jdbc:oracle:thin:@localhost/xe"); | |||
} | |||
@Test | |||
public void sanitize_sqlserver_url() { | |||
// see examples listed at https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url | |||
verifyJdbcUrl("jdbc:sqlserver://localhost;user=MyUserName;password=*****;", "jdbc:sqlserver://localhost"); | |||
verifyJdbcUrl("jdbc:sqlserver://;servername=server_name;integratedSecurity=true;authenticationScheme=JavaKerberos", "jdbc:sqlserver://server_name"); | |||
verifyJdbcUrl("jdbc:sqlserver://localhost;integratedSecurity=true;", "jdbc:sqlserver://localhost"); | |||
verifyJdbcUrl("jdbc:sqlserver://localhost;databaseName=AdventureWorks;integratedSecurity=true;", "jdbc:sqlserver://localhost/adventureworks"); | |||
verifyJdbcUrl("jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks;integratedSecurity=true;", "jdbc:sqlserver://localhost:1433/adventureworks"); | |||
verifyJdbcUrl("jdbc:sqlserver://localhost;databaseName=AdventureWorks;integratedSecurity=true;applicationName=MyApp;", "jdbc:sqlserver://localhost/adventureworks"); | |||
verifyJdbcUrl("jdbc:sqlserver://localhost;instanceName=instance1;integratedSecurity=true;", "jdbc:sqlserver://localhost"); | |||
verifyJdbcUrl("jdbc:sqlserver://;serverName=3ffe:8311:eeee:f70f:0:5eae:10.203.31.9\\\\instance1;integratedSecurity=true;", "jdbc:sqlserver://3ffe:8311:eeee:f70f:0:5eae:10.203.31.9\\\\instance1"); | |||
// test parameter aliases | |||
verifyJdbcUrl("jdbc:sqlserver://;server=server_name", "jdbc:sqlserver://server_name"); | |||
verifyJdbcUrl("jdbc:sqlserver://;server=server_name;portNumber=1234", "jdbc:sqlserver://server_name:1234"); | |||
verifyJdbcUrl("jdbc:sqlserver://;server=server_name;port=1234", "jdbc:sqlserver://server_name:1234"); | |||
// case-insensitive | |||
verifyJdbcUrl("jdbc:sqlserver://LOCALHOST;user=MyUserName;password=*****;", "jdbc:sqlserver://localhost"); | |||
} | |||
@Test | |||
public void sanitize_postgres_url() { | |||
verifyJdbcUrl("jdbc:postgresql://localhost/sonar", "jdbc:postgresql://localhost/sonar"); | |||
verifyJdbcUrl("jdbc:postgresql://localhost:1234/sonar", "jdbc:postgresql://localhost:1234/sonar"); | |||
verifyJdbcUrl("jdbc:postgresql://localhost:1234/sonar?foo", "jdbc:postgresql://localhost:1234/sonar"); | |||
// case-insensitive | |||
verifyJdbcUrl("jdbc:postgresql://localhost:1234/SONAR?foo", "jdbc:postgresql://localhost:1234/sonar"); | |||
} | |||
private static void verifyJdbcUrl(String url, String expectedResult) { | |||
assertThat(ServerIdChecksum.sanitizeJdbcUrl(url)).isEqualTo(expectedResult); | |||
} | |||
} |
@@ -19,12 +19,14 @@ | |||
*/ | |||
package org.sonar.server.platform; | |||
import org.junit.After; | |||
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.config.Configuration; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.internal.SonarRuntimeImpl; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.Version; | |||
@@ -33,6 +35,7 @@ import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.property.PropertyDto; | |||
import org.sonar.server.property.InternalProperties; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
@@ -41,136 +44,215 @@ import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE; | |||
import static org.sonar.api.SonarQubeSide.SERVER; | |||
public class ServerIdManagerTest { | |||
private static final Version SOME_VERSION = Version.create(5, 6); | |||
private static final String SOME_UUID = "some uuid"; | |||
private static final String A_SERVER_ID = "uuid"; | |||
private static final String A_JDBC_URL = "jdbc:postgres:foo"; | |||
private static final String A_VALID_CHECKSUM = ServerIdChecksum.of(A_SERVER_ID, A_JDBC_URL); | |||
@Rule | |||
public final DbTester dbTester = DbTester.create(System2.INSTANCE); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private Configuration config = new MapSettings().setProperty("sonar.jdbc.url", A_JDBC_URL).asConfig(); | |||
private DbClient dbClient = dbTester.getDbClient(); | |||
private DbSession dbSession = dbTester.getSession(); | |||
private WebServer webServer = mock(WebServer.class); | |||
private UuidFactory uuidFactory = mock(UuidFactory.class); | |||
private ServerIdManager underTest; | |||
private static SonarRuntime runtimeFor(SonarQubeSide side) { | |||
return SonarRuntimeImpl.forSonarQube(SOME_VERSION, side); | |||
@After | |||
public void tearDown() { | |||
if (underTest != null) { | |||
underTest.stop(); | |||
} | |||
} | |||
@Test | |||
public void start_persists_new_serverId_if_server_startupLeader_and_serverId_does_not_exist() { | |||
when(uuidFactory.create()).thenReturn(SOME_UUID); | |||
public void web_leader_persists_new_server_id_if_missing() { | |||
when(uuidFactory.create()).thenReturn(A_SERVER_ID); | |||
when(webServer.isStartupLeader()).thenReturn(true); | |||
new ServerIdManager(dbClient, runtimeFor(SERVER), webServer, uuidFactory) | |||
.start(); | |||
test(SERVER); | |||
assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID)) | |||
.extracting(PropertyDto::getValue) | |||
.containsOnly(SOME_UUID); | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
@Test | |||
public void start_persists_new_serverId_if_server_startupLeader_and_serverId_is_an_old_date() { | |||
insertPropertyCoreId("20161123150657"); | |||
when(uuidFactory.create()).thenReturn(SOME_UUID); | |||
public void web_leader_persists_new_server_id_if_format_is_old_date() { | |||
insertServerId("20161123150657"); | |||
when(uuidFactory.create()).thenReturn(A_SERVER_ID); | |||
when(webServer.isStartupLeader()).thenReturn(true); | |||
new ServerIdManager(dbClient, runtimeFor(SERVER), webServer, uuidFactory) | |||
.start(); | |||
test(SERVER); | |||
assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID)) | |||
.extracting(PropertyDto::getValue) | |||
.containsOnly(SOME_UUID); | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
private void insertPropertyCoreId(String value) { | |||
dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(value)); | |||
dbSession.commit(); | |||
@Test | |||
public void web_leader_persists_new_server_id_if_value_is_empty() { | |||
insertServerId(""); | |||
when(uuidFactory.create()).thenReturn(A_SERVER_ID); | |||
when(webServer.isStartupLeader()).thenReturn(true); | |||
test(SERVER); | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
@Test | |||
public void start_persists_new_serverId_if_server_startupLeader_and_serverId_is_empty() { | |||
insertPropertyCoreId(""); | |||
when(uuidFactory.create()).thenReturn(SOME_UUID); | |||
public void web_leader_keeps_existing_server_id_if_valid() { | |||
insertServerId(A_SERVER_ID); | |||
insertChecksum(A_VALID_CHECKSUM); | |||
when(webServer.isStartupLeader()).thenReturn(true); | |||
new ServerIdManager(dbClient, runtimeFor(SERVER), webServer, uuidFactory) | |||
.start(); | |||
test(SERVER); | |||
assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID)) | |||
.extracting(PropertyDto::getValue) | |||
.containsOnly(SOME_UUID); | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
@Test | |||
public void web_leader_resets_server_id_if_invalid() { | |||
insertServerId("foo"); | |||
insertChecksum("invalid"); | |||
when(uuidFactory.create()).thenReturn(A_SERVER_ID); | |||
when(webServer.isStartupLeader()).thenReturn(true); | |||
test(SERVER); | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
@Test | |||
public void web_leader_generates_missing_checksum() { | |||
insertServerId(A_SERVER_ID); | |||
when(webServer.isStartupLeader()).thenReturn(true); | |||
test(SERVER); | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
@Test | |||
public void start_fails_with_ISE_if_serverId_is_null_and_server_is_not_startupLeader() { | |||
public void web_follower_does_not_fail_if_server_id_is_valid() { | |||
insertServerId(A_SERVER_ID); | |||
insertChecksum(A_VALID_CHECKSUM); | |||
when(webServer.isStartupLeader()).thenReturn(false); | |||
ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(SERVER), webServer, uuidFactory); | |||
test(SERVER); | |||
expectMissingCoreIdException(); | |||
underTest.start(); | |||
// no changes | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
@Test | |||
public void start_fails_with_ISE_if_serverId_is_empty_and_server_is_not_startupLeader() { | |||
insertPropertyCoreId(""); | |||
public void web_follower_fails_if_server_id_is_missing() { | |||
when(webServer.isStartupLeader()).thenReturn(false); | |||
ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(SERVER), webServer, uuidFactory); | |||
expectMissingServerIdException(); | |||
expectEmptyCoreIdException(); | |||
test(SERVER); | |||
} | |||
underTest.start(); | |||
@Test | |||
public void web_follower_fails_if_server_id_is_empty() { | |||
insertServerId(""); | |||
when(webServer.isStartupLeader()).thenReturn(false); | |||
expectEmptyServerIdException(); | |||
test(SERVER); | |||
} | |||
@Test | |||
public void start_fails_with_ISE_if_serverId_is_null_and_not_server() { | |||
public void web_follower_fails_if_server_id_is_invalid() { | |||
insertServerId(A_SERVER_ID); | |||
insertChecksum("boom"); | |||
when(webServer.isStartupLeader()).thenReturn(false); | |||
ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(COMPUTE_ENGINE), webServer, uuidFactory); | |||
expectInvalidServerIdException(); | |||
expectMissingCoreIdException(); | |||
test(SERVER); | |||
underTest.start(); | |||
// no changes | |||
verifyDb(A_SERVER_ID, "boom"); | |||
} | |||
@Test | |||
public void start_fails_with_ISE_if_serverId_is_empty_and_not_server() { | |||
insertPropertyCoreId(""); | |||
public void compute_engine_does_not_fail_if_server_id_is_valid() { | |||
insertServerId(A_SERVER_ID); | |||
insertChecksum(A_VALID_CHECKSUM); | |||
ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(COMPUTE_ENGINE), webServer, uuidFactory); | |||
test(COMPUTE_ENGINE); | |||
expectEmptyCoreIdException(); | |||
// no changes | |||
verifyDb(A_SERVER_ID, A_VALID_CHECKSUM); | |||
} | |||
underTest.start(); | |||
@Test | |||
public void compute_engine_fails_if_server_id_is_missing() { | |||
expectMissingServerIdException(); | |||
test(COMPUTE_ENGINE); | |||
} | |||
@Test | |||
public void start_does_not_fail_if_serverId_exists_and_server_is_not_startupLeader() { | |||
insertPropertyCoreId(SOME_UUID); | |||
when(webServer.isStartupLeader()).thenReturn(false); | |||
public void compute_engine_fails_if_server_id_is_empty() { | |||
insertServerId(""); | |||
expectEmptyServerIdException(); | |||
new ServerIdManager(dbClient, runtimeFor(SERVER), webServer, uuidFactory).start(); | |||
test(COMPUTE_ENGINE); | |||
} | |||
@Test | |||
public void start_does_not_fail_if_serverId_exists_and_not_server() { | |||
insertPropertyCoreId(SOME_UUID); | |||
public void compute_engine_fails_if_server_id_is_invalid() { | |||
insertServerId(A_SERVER_ID); | |||
insertChecksum("boom"); | |||
expectInvalidServerIdException(); | |||
test(COMPUTE_ENGINE); | |||
new ServerIdManager(dbClient, runtimeFor(COMPUTE_ENGINE), webServer, uuidFactory).start(); | |||
// no changes | |||
verifyDb(A_SERVER_ID, "boom"); | |||
} | |||
private void expectEmptyCoreIdException() { | |||
private void expectEmptyServerIdException() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Property sonar.core.id is set but empty in database"); | |||
expectedException.expectMessage("Property sonar.core.id is empty in database"); | |||
} | |||
private void expectMissingCoreIdException() { | |||
private void expectMissingServerIdException() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Property sonar.core.id is missing in database"); | |||
} | |||
private void expectInvalidServerIdException() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Server ID is invalid"); | |||
} | |||
private void verifyDb(String expectedServerId, String expectedChecksum) { | |||
assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID)) | |||
.extracting(PropertyDto::getValue) | |||
.containsExactly(expectedServerId); | |||
assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM)) | |||
.hasValue(expectedChecksum); | |||
} | |||
private void insertServerId(String value) { | |||
dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(value)); | |||
dbSession.commit(); | |||
} | |||
private void insertChecksum(String value) { | |||
dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, value); | |||
dbSession.commit(); | |||
} | |||
private void test(SonarQubeSide side) { | |||
underTest = new ServerIdManager(config, dbClient, SonarRuntimeImpl.forSonarQube(Version.create(6, 7), side), webServer, uuidFactory); | |||
underTest.start(); | |||
} | |||
} |
@@ -5,8 +5,10 @@ | |||
#-------------------------------------------------------------------------------------------------- | |||
# DATABASE | |||
# | |||
# IMPORTANT: the embedded H2 database is used by default. It is recommended for tests but not for | |||
# production use. Supported databases are MySQL, Oracle, PostgreSQL and Microsoft SQLServer. | |||
# IMPORTANT: | |||
# - The embedded H2 database is used by default. It is recommended for tests but not for | |||
# production use. Supported databases are MySQL, Oracle, PostgreSQL and Microsoft SQLServer. | |||
# - Changes to database connection URL (sonar.jdbc.url) can affect SonarSource licensed products. | |||
# User credentials. | |||
# Permissions to create tables, indices and triggers must be granted to JDBC user. |