Browse Source

LICENSE-82 reset server ID on DB URL changes

tags/7.0-RC1
Simon Brandhof 6 years ago
parent
commit
8bc85e2ccc

+ 13
- 6
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

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

+ 5
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java View File

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

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/platform/BackendCleanup.java View File

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

+ 99
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdChecksum.java View File

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

+ 75
- 17
server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdManager.java View File

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

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/property/InternalProperties.java View File

@@ -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.
*

+ 95
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdChecksumTest.java View File

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

+ 142
- 60
server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java View File

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

+ 4
- 2
sonar-application/src/main/assembly/conf/sonar.properties View File

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

Loading…
Cancel
Save