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;
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;
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
@Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ public DbTester db = DbTester.create(System2.INSTANCE);
private ComputeEngineContainerImpl underTest;
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));
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;
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();
}
}
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;
return db.getDatabase();
}
+ public String getUrl() {
+ return ((BasicDataSource) db.getDatabase().getDataSource()).getUrl();
+ }
+
public DatabaseCommands getCommands() {
return db.getCommands();
}
* 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();
--- /dev/null
+/*
+ * 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();
+ }
+}
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;
@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));
}
/**
}
}
- 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
String ORGANIZATION_ENABLED = "organization.enabled";
+ String SERVER_ID_CHECKSUM = "server.idChecksum";
/**
* Read the value of the specified property.
*
--- /dev/null
+/*
+ * 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);
+ }
+}
*/
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;
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;
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();
+ }
}
#--------------------------------------------------------------------------------------------------
# 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.