aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java6
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java13
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/JdbcUrlSanitizer.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdChecksum.java)18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdChecksum.java42
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactory.java34
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactoryImpl.java69
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdManager.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdManager.java)118
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdModule.java35
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/serverid/package-info.java23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java258
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/serverid/JdbcUrlSanitizerTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdChecksumTest.java)21
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdChecksumTest.java64
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdFactoryImplTest.java119
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java361
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ServerId.java164
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ServerIdTest.java309
17 files changed, 1310 insertions, 348 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index 0964c3cfe22..2a474e1feca 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -117,7 +117,6 @@ import org.sonar.server.permission.ws.template.DefaultTemplatesResolverImpl;
import org.sonar.server.platform.DatabaseServerCompatibility;
import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.ServerFileSystemImpl;
-import org.sonar.server.platform.ServerIdManager;
import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.ServerLifecycleNotifier;
import org.sonar.server.platform.ServerLogging;
@@ -128,7 +127,9 @@ import org.sonar.server.platform.WebServerImpl;
import org.sonar.server.platform.db.migration.MigrationConfigurationModule;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
import org.sonar.server.platform.monitoring.DbSection;
+import org.sonar.server.platform.monitoring.OfficialDistribution;
import org.sonar.server.platform.monitoring.cluster.ProcessInfoProvider;
+import org.sonar.server.platform.serverid.ServerIdModule;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.ServerExtensionInstaller;
import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
@@ -305,7 +306,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
private static void populateLevel3(ComponentContainer container) {
container.add(
new StartupMetadataProvider(),
- ServerIdManager.class,
+ ServerIdModule.class,
UriReader.class,
ServerImpl.class,
DefaultOrganizationProviderImpl.class);
@@ -416,6 +417,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
CeTaskCommonsModule.class,
ProjectAnalysisTaskModule.class,
CeTaskProcessorModule.class,
+ OfficialDistribution.class,
InternalPropertiesImpl.class,
ProjectConfigurationFactory.class,
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index dbb3ee1eafe..b617004521b 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -22,8 +22,11 @@ package org.sonar.ce.container;
import java.io.File;
import java.io.IOException;
import java.util.Date;
+import java.util.Locale;
import java.util.Properties;
import java.util.stream.Collectors;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -41,7 +44,6 @@ 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;
@@ -82,7 +84,7 @@ public class ComputeEngineContainerImplTest {
// required persisted properties
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()));
+ insertInternalProperty(InternalProperties.SERVER_ID_CHECKSUM, DigestUtils.sha256Hex("a_server_id|" + cleanJdbcUrl()));
underTest
.start(new Props(properties));
@@ -91,7 +93,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
- + 75 // level 4
+ + 76 // level 4
+ 6 // content of CeConfigurationModule
+ 4 // content of CeQueueModule
+ 4 // content of CeHttpModule
@@ -105,6 +107,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
+ 5 // level 3
+ + 4 // content of ServerIdModule
);
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
@@ -133,6 +136,10 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getLifecycleState().isDisposed()).isTrue();
}
+ private String cleanJdbcUrl() {
+ return StringUtils.lowerCase(StringUtils.substringBefore(db.getUrl(), "?"), Locale.ENGLISH);
+ }
+
private Properties getProperties() throws IOException {
Properties properties = ProcessProperties.defaults();
File homeDir = tempFolder.newFolder();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
index d7c3a3753b6..5bd0720e160 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
@@ -23,10 +23,10 @@ import org.sonar.api.utils.UriReader;
import org.sonar.core.util.DefaultHttpDownloader;
import org.sonar.server.organization.DefaultOrganizationProviderImpl;
import org.sonar.server.organization.OrganizationFlagsImpl;
-import org.sonar.server.platform.ServerIdManager;
import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.StartupMetadataPersister;
import org.sonar.server.platform.db.migration.NoopDatabaseMigrationImpl;
+import org.sonar.server.platform.serverid.ServerIdModule;
import org.sonar.server.setting.DatabaseSettingLoader;
import org.sonar.server.setting.DatabaseSettingsEnabler;
@@ -40,7 +40,7 @@ public class PlatformLevel3 extends PlatformLevel {
addIfStartupLeader(StartupMetadataPersister.class);
add(
NoopDatabaseMigrationImpl.class,
- ServerIdManager.class,
+ ServerIdModule.class,
ServerImpl.class,
DatabaseSettingLoader.class,
DatabaseSettingsEnabler.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdChecksum.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/JdbcUrlSanitizer.java
index 66ca4f448aa..f888c6963a4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdChecksum.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/JdbcUrlSanitizer.java
@@ -17,32 +17,20 @@
* 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;
+package org.sonar.server.platform.serverid;
-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 {
-
+public class JdbcUrlSanitizer {
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) {
+ public String sanitize(String jdbcUrl) {
String result;
if (jdbcUrl.startsWith(SQLSERVER_PREFIX)) {
result = sanitizeSqlServerUrl(jdbcUrl);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdChecksum.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdChecksum.java
new file mode 100644
index 00000000000..ee3e730c925
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdChecksum.java
@@ -0,0 +1,42 @@
+/*
+ * 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.serverid;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.sonar.api.config.Configuration;
+
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+
+public class ServerIdChecksum {
+
+ private final Configuration config;
+ private final JdbcUrlSanitizer jdbcUrlSanitizer;
+
+ public ServerIdChecksum(Configuration config, JdbcUrlSanitizer jdbcUrlSanitizer) {
+ this.config = config;
+ this.jdbcUrlSanitizer = jdbcUrlSanitizer;
+ }
+
+ public String computeFor(String serverId) {
+ String jdbcUrl = config.get(JDBC_URL).orElseThrow(() -> new IllegalStateException("Missing JDBC URL"));
+ return DigestUtils.sha256Hex(serverId + "|" + jdbcUrlSanitizer.sanitize(jdbcUrl));
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactory.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactory.java
new file mode 100644
index 00000000000..33c419682ec
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.serverid;
+
+import org.sonar.core.platform.ServerId;
+
+public interface ServerIdFactory {
+ /**
+ * Create a new ServerId from scratch.
+ */
+ ServerId create();
+
+ /**
+ * Create a new ServerId from the current serverId.
+ */
+ ServerId create(ServerId currentServerId);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactoryImpl.java
new file mode 100644
index 00000000000..899b7cabf85
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactoryImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.serverid;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.zip.CRC32;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.config.Configuration;
+import org.sonar.core.platform.ServerId;
+import org.sonar.core.util.UuidFactory;
+
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+
+public class ServerIdFactoryImpl implements ServerIdFactory {
+
+ private final Configuration config;
+ private final UuidFactory uuidFactory;
+ private final JdbcUrlSanitizer jdbcUrlSanitizer;
+
+ public ServerIdFactoryImpl(Configuration config, UuidFactory uuidFactory, JdbcUrlSanitizer jdbcUrlSanitizer) {
+ this.config = config;
+ this.uuidFactory = uuidFactory;
+ this.jdbcUrlSanitizer = jdbcUrlSanitizer;
+ }
+
+ @Override
+ public ServerId create() {
+ return ServerId.of(computeDatabaseId(), uuidFactory.create());
+ }
+
+ @Override
+ public ServerId create(ServerId currentServerId) {
+ return ServerId.of(computeDatabaseId(), currentServerId.getDatasetId());
+ }
+
+ private String computeDatabaseId() {
+ String jdbcUrl = config.get(JDBC_URL).orElseThrow(() -> new IllegalStateException("Missing JDBC URL"));
+ return crc32Hex(jdbcUrlSanitizer.sanitize(jdbcUrl));
+ }
+
+ @VisibleForTesting
+ static String crc32Hex(String str) {
+ CRC32 crc32 = new CRC32();
+ crc32.update(str.getBytes(StandardCharsets.UTF_8));
+ long hash = crc32.getValue();
+ String s = Long.toHexString(hash).toUpperCase(Locale.ENGLISH);
+ return StringUtils.leftPad(s, ServerId.DATABASE_ID_LENGTH, "0");
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdManager.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdManager.java
index fe465c595b1..7b7992716b7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdManager.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdManager.java
@@ -17,55 +17,57 @@
* 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;
+package org.sonar.server.platform.serverid;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
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.core.platform.ServerId;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.WebServer;
import org.sonar.server.property.InternalProperties;
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_ID;
+import static org.sonar.core.platform.ServerId.Format.DEPRECATED;
+import static org.sonar.core.platform.ServerId.Format.NO_DATABASE_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 ServerIdChecksum serverIdChecksum;
+ private final ServerIdFactory serverIdFactory;
private final DbClient dbClient;
private final SonarRuntime runtime;
private final WebServer webServer;
- private final UuidFactory uuidFactory;
- public ServerIdManager(Configuration config, DbClient dbClient, SonarRuntime runtime, WebServer webServer, UuidFactory uuidFactory) {
- this.config = config;
+ public ServerIdManager(ServerIdChecksum serverIdChecksum, ServerIdFactory serverIdFactory, DbClient dbClient, SonarRuntime runtime, WebServer webServer) {
+ this.serverIdChecksum = serverIdChecksum;
+ this.serverIdFactory = serverIdFactory;
this.dbClient = dbClient;
this.runtime = runtime;
this.webServer = webServer;
- this.uuidFactory = uuidFactory;
}
@Override
public void start() {
try (DbSession dbSession = dbClient.openSession(false)) {
if (runtime.getSonarQubeSide() == SonarQubeSide.SERVER && webServer.isStartupLeader()) {
- if (needsToBeDropped(dbSession)) {
- dbClient.propertiesDao().deleteGlobalProperty(SERVER_ID, dbSession);
- }
- persistServerIdIfMissing(dbSession);
+ Optional<String> checksum = dbClient.internalPropertiesDao().selectByKey(dbSession, SERVER_ID_CHECKSUM);
+
+ ServerId serverId = readCurrentServerId(dbSession)
+ .map(currentServerId -> keepOrReplaceCurrentServerId(dbSession, currentServerId, checksum))
+ .orElseGet(() -> createFirstServerId(dbSession));
+ updateChecksum(dbSession, serverId);
+
dbSession.commit();
} else {
ensureServerIdIsValid(dbSession);
@@ -73,67 +75,75 @@ public class ServerIdManager implements Startable {
}
}
- 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;
+ private ServerId keepOrReplaceCurrentServerId(DbSession dbSession, ServerId currentServerId, Optional<String> checksum) {
+ if (keepServerId(currentServerId, checksum)) {
+ return currentServerId;
}
- if (isEmpty(dto.getValue())) {
- return true;
- }
+ ServerId serverId = replaceCurrentServerId(currentServerId);
+ persistServerId(dbSession, serverId);
+ return serverId;
+ }
- if (isDate(dto.getValue())) {
+ private boolean keepServerId(ServerId serverId, Optional<String> checksum) {
+ ServerId.Format format = serverId.getFormat();
+ if (format == DEPRECATED || format == NO_DATABASE_ID) {
LOGGER.info("Server ID is changed to new format.");
- return true;
+ return false;
}
- Optional<String> checksum = dbClient.internalPropertiesDao().selectByKey(dbSession, SERVER_ID_CHECKSUM);
if (checksum.isPresent()) {
- String expectedChecksum = computeChecksum(dto.getValue());
+ String expectedChecksum = serverIdChecksum.computeFor(serverId.toString());
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;
+ return false;
}
}
- // Existing server ID must be kept when upgrading to 6.7+. In that case the checksum does
- // not exist.
+ // Existing server ID must be kept when upgrading to 6.7+. In that case the checksum does not exist.
+ return true;
+ }
- return false;
+ private ServerId replaceCurrentServerId(ServerId currentServerId) {
+ if (currentServerId.getFormat() == DEPRECATED) {
+ return serverIdFactory.create();
+ }
+ return serverIdFactory.create(currentServerId);
}
- 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();
+ private ServerId createFirstServerId(DbSession dbSession) {
+ ServerId serverId = serverIdFactory.create();
+ persistServerId(dbSession, serverId);
+ return serverId;
+ }
+
+ private Optional<ServerId> readCurrentServerId(DbSession dbSession) {
+ PropertyDto dto = dbClient.propertiesDao().selectGlobalProperty(dbSession, SERVER_ID);
+ if (dto == null) {
+ return Optional.empty();
+ }
+
+ String value = dto.getValue();
+ if (isEmpty(value)) {
+ return Optional.empty();
}
+ return Optional.of(ServerId.parse(value));
+ }
+
+ private void updateChecksum(DbSession dbSession, ServerId serverId) {
// 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));
+ String checksum = serverIdChecksum.computeFor(serverId.toString());
+ persistChecksum(dbSession, checksum);
}
- /**
- * Checks whether the specified value is a date according to the old format of the {@link CoreProperties#SERVER_ID}.
- */
- private static boolean isDate(String value) {
- try {
- new SimpleDateFormat("yyyyMMddHHmmss").parse(value);
- return true;
- } catch (ParseException e) {
- return false;
- }
+ private void persistServerId(DbSession dbSession, ServerId serverId) {
+ dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(SERVER_ID).setValue(serverId.toString()));
}
- 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 persistChecksum(DbSession dbSession, String checksump) {
+ dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, checksump);
}
private void ensureServerIdIsValid(DbSession dbSession) {
@@ -143,7 +153,7 @@ public class ServerIdManager implements Startable {
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");
+ checkState(checksum.get().equals(serverIdChecksum.computeFor(id.getValue())), "Server ID is invalid");
}
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdModule.java
new file mode 100644
index 00000000000..2ef62d182af
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdModule.java
@@ -0,0 +1,35 @@
+/*
+ * 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.serverid;
+
+import org.sonar.core.platform.Module;
+
+public class ServerIdModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ ServerIdFactoryImpl.class,
+ JdbcUrlSanitizer.class,
+ ServerIdChecksum.class,
+ ServerIdManager.class
+
+ );
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/package-info.java
new file mode 100644
index 00000000000..31970500810
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/serverid/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.platform.serverid;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java
deleted file mode 100644
index 5c594a0dbb3..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.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.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.core.util.UuidFactory;
-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;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE;
-import static org.sonar.api.SonarQubeSide.SERVER;
-
-public class ServerIdManagerTest {
-
- 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;
-
- @After
- public void tearDown() {
- if (underTest != null) {
- underTest.stop();
- }
- }
-
- @Test
- public void web_leader_persists_new_server_id_if_missing() {
- 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_persists_new_server_id_if_format_is_old_date() {
- insertServerId("20161123150657");
- 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_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 web_leader_keeps_existing_server_id_if_valid() {
- insertServerId(A_SERVER_ID);
- insertChecksum(A_VALID_CHECKSUM);
- when(webServer.isStartupLeader()).thenReturn(true);
-
- test(SERVER);
-
- 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 web_follower_does_not_fail_if_server_id_is_valid() {
- insertServerId(A_SERVER_ID);
- insertChecksum(A_VALID_CHECKSUM);
- when(webServer.isStartupLeader()).thenReturn(false);
-
- test(SERVER);
-
- // no changes
- verifyDb(A_SERVER_ID, A_VALID_CHECKSUM);
- }
-
- @Test
- public void web_follower_fails_if_server_id_is_missing() {
- when(webServer.isStartupLeader()).thenReturn(false);
-
- expectMissingServerIdException();
-
- test(SERVER);
- }
-
- @Test
- public void web_follower_fails_if_server_id_is_empty() {
- insertServerId("");
- when(webServer.isStartupLeader()).thenReturn(false);
-
- expectEmptyServerIdException();
-
- test(SERVER);
- }
-
- @Test
- public void web_follower_fails_if_server_id_is_invalid() {
- insertServerId(A_SERVER_ID);
- insertChecksum("boom");
- when(webServer.isStartupLeader()).thenReturn(false);
-
- expectInvalidServerIdException();
-
- test(SERVER);
-
- // no changes
- verifyDb(A_SERVER_ID, "boom");
- }
-
- @Test
- public void compute_engine_does_not_fail_if_server_id_is_valid() {
- insertServerId(A_SERVER_ID);
- insertChecksum(A_VALID_CHECKSUM);
-
- test(COMPUTE_ENGINE);
-
- // no changes
- verifyDb(A_SERVER_ID, A_VALID_CHECKSUM);
- }
-
- @Test
- public void compute_engine_fails_if_server_id_is_missing() {
- expectMissingServerIdException();
-
- test(COMPUTE_ENGINE);
- }
-
- @Test
- public void compute_engine_fails_if_server_id_is_empty() {
- insertServerId("");
-
- expectEmptyServerIdException();
-
- test(COMPUTE_ENGINE);
- }
-
- @Test
- public void compute_engine_fails_if_server_id_is_invalid() {
- insertServerId(A_SERVER_ID);
- insertChecksum("boom");
-
- expectInvalidServerIdException();
-
- test(COMPUTE_ENGINE);
-
- // no changes
- verifyDb(A_SERVER_ID, "boom");
- }
-
- private void expectEmptyServerIdException() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Property sonar.core.id is empty in database");
- }
-
- 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();
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdChecksumTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/JdbcUrlSanitizerTest.java
index dada79edc40..831ba921867 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdChecksumTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/JdbcUrlSanitizerTest.java
@@ -17,23 +17,14 @@
* 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;
+package org.sonar.server.platform.serverid;
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"));
- }
+public class JdbcUrlSanitizerTest {
+ private JdbcUrlSanitizer underTest = new JdbcUrlSanitizer();
@Test
public void sanitize_h2_url() {
@@ -89,7 +80,9 @@ public class ServerIdChecksumTest {
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);
+ private void verifyJdbcUrl(String url, String expectedResult) {
+ assertThat(underTest.sanitize(url)).isEqualTo(expectedResult);
}
+
+
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdChecksumTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdChecksumTest.java
new file mode 100644
index 00000000000..ebde1280e20
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdChecksumTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.serverid;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+
+public class ServerIdChecksumTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void compute_throws_ISE_if_jdbcUrl_property_is_not_set() {
+ ServerIdChecksum underTest = new ServerIdChecksum(new MapSettings().asConfig(), null /*doesn't matter*/);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Missing JDBC URL");
+
+ underTest.computeFor("foo");
+ }
+
+ @Test
+ public void test_checksum() {
+ assertThat(computeFor("id1", "url1"))
+ .isNotEmpty()
+ .isEqualTo(computeFor("id1", "url1"))
+ .isNotEqualTo(computeFor("id1", "url2"))
+ .isNotEqualTo(computeFor("id2", "url1"))
+ .isNotEqualTo(computeFor("id2", "url2"));
+ }
+
+ private String computeFor(String serverId, String jdbcUrl) {
+ MapSettings settings = new MapSettings();
+ JdbcUrlSanitizer jdbcUrlSanitizer = mock(JdbcUrlSanitizer.class);
+ when(jdbcUrlSanitizer.sanitize(jdbcUrl)).thenReturn("_" + jdbcUrl);
+ ServerIdChecksum underTest = new ServerIdChecksum(settings.asConfig(), jdbcUrlSanitizer);
+ settings.setProperty(JDBC_URL, jdbcUrl);
+ return underTest.computeFor(serverId);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdFactoryImplTest.java
new file mode 100644
index 00000000000..0379a801f68
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdFactoryImplTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.serverid;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.core.platform.ServerId;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.Uuids;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH;
+import static org.sonar.process.ProcessProperties.JDBC_URL;
+import static org.sonar.server.platform.serverid.ServerIdFactoryImpl.crc32Hex;
+
+@RunWith(DataProviderRunner.class)
+public class ServerIdFactoryImplTest {
+ private static final ServerId A_SERVERID = ServerId.of(randomAlphabetic(DATABASE_ID_LENGTH), randomAlphabetic(UUID_DATASET_ID_LENGTH));
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MapSettings settings = new MapSettings();
+ private Configuration config = settings.asConfig();
+ private UuidFactory uuidFactory = mock(UuidFactory.class);
+ private JdbcUrlSanitizer jdbcUrlSanitizer = mock(JdbcUrlSanitizer.class);
+ private ServerIdFactoryImpl underTest = new ServerIdFactoryImpl(config, uuidFactory, jdbcUrlSanitizer);
+
+ @Test
+ public void create_from_scratch_fails_with_ISE_if_JDBC_property_not_set() {
+ expectMissingJdbcUrlISE();
+
+ underTest.create();
+ }
+
+ @Test
+ public void create_from_scratch_creates_ServerId_from_JDBC_URL_and_new_uuid() {
+ String jdbcUrl = "jdbc";
+ String uuid = Uuids.create();
+ String sanitizedJdbcUrl = "sanitized_jdbc";
+ settings.setProperty(JDBC_URL, jdbcUrl);
+ when(uuidFactory.create()).thenReturn(uuid);
+ when(jdbcUrlSanitizer.sanitize(jdbcUrl)).thenReturn(sanitizedJdbcUrl);
+
+ ServerId serverId = underTest.create();
+
+ assertThat(serverId.getDatabaseId().get()).isEqualTo(crc32Hex(sanitizedJdbcUrl));
+ assertThat(serverId.getDatasetId()).isEqualTo(uuid);
+ }
+
+ @Test
+ public void create_from_ServerId_fails_with_ISE_if_JDBC_property_not_set() {
+ expectMissingJdbcUrlISE();
+
+ underTest.create(A_SERVERID);
+ }
+
+ @Test
+ @UseDataProvider("anyFormatServerId")
+ public void create_from_ServerId_creates_ServerId_from_JDBC_URL_and_serverId_datasetId(ServerId currentServerId) {
+ String jdbcUrl = "jdbc";
+ String sanitizedJdbcUrl = "sanitized_jdbc";
+ settings.setProperty(JDBC_URL, jdbcUrl);
+ when(uuidFactory.create()).thenThrow(new IllegalStateException("UuidFactory.create() should not be called"));
+ when(jdbcUrlSanitizer.sanitize(jdbcUrl)).thenReturn(sanitizedJdbcUrl);
+
+ ServerId serverId = underTest.create(currentServerId);
+
+ assertThat(serverId.getDatabaseId().get()).isEqualTo(crc32Hex(sanitizedJdbcUrl));
+ assertThat(serverId.getDatasetId()).isEqualTo(currentServerId.getDatasetId());
+ }
+
+ @DataProvider
+ public static Object[][] anyFormatServerId() {
+ return new Object[][] {
+ {ServerId.parse(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()))},
+ {ServerId.parse(randomAlphabetic(NOT_UUID_DATASET_ID_LENGTH))},
+ {ServerId.parse(randomAlphabetic(UUID_DATASET_ID_LENGTH))},
+ {ServerId.of(randomAlphabetic(DATABASE_ID_LENGTH), randomAlphabetic(NOT_UUID_DATASET_ID_LENGTH))},
+ {ServerId.of(randomAlphabetic(DATABASE_ID_LENGTH), randomAlphabetic(UUID_DATASET_ID_LENGTH))}
+ };
+ }
+
+ private void expectMissingJdbcUrlISE() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Missing JDBC URL");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java
new file mode 100644
index 00000000000..855ad3a0496
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.serverid;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.Version;
+import org.sonar.core.platform.ServerId;
+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.platform.WebServer;
+import org.sonar.server.property.InternalProperties;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE;
+import static org.sonar.api.SonarQubeSide.SERVER;
+import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH;
+
+@RunWith(DataProviderRunner.class)
+public class ServerIdManagerTest {
+
+ private static final ServerId OLD_FORMAT_SERVER_ID = ServerId.parse("20161123150657");
+ private static final ServerId NO_DATABASE_ID_SERVER_ID = ServerId.parse(randomAlphanumeric(UUID_DATASET_ID_LENGTH));
+ private static final ServerId WITH_DATABASE_ID_SERVER_ID = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(NOT_UUID_DATASET_ID_LENGTH));
+ private static final String CHECKSUM_1 = randomAlphanumeric(12);
+
+ @Rule
+ public final DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private ServerIdChecksum serverIdChecksum = mock(ServerIdChecksum.class);
+ private ServerIdFactory serverIdFactory = mock(ServerIdFactory.class);
+ private DbClient dbClient = dbTester.getDbClient();
+ private DbSession dbSession = dbTester.getSession();
+ private WebServer webServer = mock(WebServer.class);
+ private ServerIdManager underTest;
+
+ @After
+ public void tearDown() {
+ if (underTest != null) {
+ underTest.stop();
+ }
+ }
+
+ @Test
+ public void web_leader_persists_new_server_id_if_missing() {
+ mockCreateNewServerId(WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ verifyCreateNewServerIdFromScratch();
+ }
+
+ @Test
+ public void web_leader_persists_new_server_id_if_format_is_old_date() {
+ insertServerId(OLD_FORMAT_SERVER_ID);
+ mockCreateNewServerId(WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ verifyCreateNewServerIdFromScratch();
+ }
+
+ @Test
+ public void web_leader_persists_new_server_id_if_value_is_empty() {
+ insertServerId("");
+ mockCreateNewServerId(WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ verifyCreateNewServerIdFromScratch();
+ }
+
+ @Test
+ public void web_leader_keeps_existing_server_id_if_valid() {
+ insertServerId(WITH_DATABASE_ID_SERVER_ID);
+ insertChecksum(CHECKSUM_1);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ }
+
+ @Test
+ public void web_leader_creates_server_id_from_scratch_if_checksum_fails_for_serverId_in_deprecated_format() {
+ ServerId currentServerId = OLD_FORMAT_SERVER_ID;
+ insertServerId(currentServerId);
+ insertChecksum("invalid");
+ mockChecksumOf(currentServerId, "valid");
+ mockCreateNewServerId(WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ verifyCreateNewServerIdFromScratch();
+ }
+
+ @Test
+ public void web_leader_creates_server_id_from_current_serverId_without_databaseId_if_checksum_fails() {
+ ServerId currentServerId = ServerId.parse(randomAlphanumeric(UUID_DATASET_ID_LENGTH));
+ insertServerId(currentServerId);
+ insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID");
+ mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID");
+ mockCreateNewServerIdFrom(currentServerId, WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ verifyCreateNewServerIdFrom(currentServerId);
+ }
+
+ @Test
+ public void web_leader_creates_server_id_from_current_serverId_with_databaseId_if_checksum_fails() {
+ ServerId currentServerId = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(UUID_DATASET_ID_LENGTH));
+ insertServerId(currentServerId);
+ insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID");
+ mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID");
+ mockCreateNewServerIdFrom(currentServerId, WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ verifyCreateNewServerIdFrom(currentServerId);
+ }
+
+ @Test
+ public void web_leader_generates_missing_checksum_for_current_serverId_with_databaseId() {
+ insertServerId(WITH_DATABASE_ID_SERVER_ID);
+ mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(true);
+
+ test(SERVER);
+
+ verifyDb(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+ }
+
+ @Test
+ @UseDataProvider("allFormatsOfServerId")
+ public void web_follower_does_not_fail_if_server_id_matches_checksum(ServerId serverId) {
+ insertServerId(serverId);
+ insertChecksum(CHECKSUM_1);
+ mockChecksumOf(serverId, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(false);
+
+ test(SERVER);
+
+ // no changes
+ verifyDb(serverId, CHECKSUM_1);
+ }
+
+ @Test
+ public void web_follower_fails_if_server_id_is_missing() {
+ when(webServer.isStartupLeader()).thenReturn(false);
+
+ expectMissingServerIdException();
+
+ test(SERVER);
+ }
+
+ @Test
+ public void web_follower_fails_if_server_id_is_empty() {
+ insertServerId("");
+ when(webServer.isStartupLeader()).thenReturn(false);
+
+ expectEmptyServerIdException();
+
+ test(SERVER);
+ }
+
+ @Test
+ @UseDataProvider("allFormatsOfServerId")
+ public void web_follower_fails_if_checksum_does_not_match(ServerId serverId) {
+ String dbChecksum = "boom";
+ insertServerId(serverId);
+ insertChecksum(dbChecksum);
+ mockChecksumOf(serverId, CHECKSUM_1);
+ when(webServer.isStartupLeader()).thenReturn(false);
+
+ try {
+ test(SERVER);
+ fail("An ISE should have been raised");
+ }
+ catch (IllegalStateException e) {
+ assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
+ // no changes
+ verifyDb(serverId, dbChecksum);
+ }
+ }
+
+ @Test
+ @UseDataProvider("allFormatsOfServerId")
+ public void compute_engine_does_not_fail_if_server_id_is_valid(ServerId serverId) {
+ insertServerId(serverId);
+ insertChecksum(CHECKSUM_1);
+ mockChecksumOf(serverId, CHECKSUM_1);
+
+ test(COMPUTE_ENGINE);
+
+ // no changes
+ verifyDb(serverId, CHECKSUM_1);
+ }
+
+ @Test
+ public void compute_engine_fails_if_server_id_is_missing() {
+ expectMissingServerIdException();
+
+ test(COMPUTE_ENGINE);
+ }
+
+ @Test
+ public void compute_engine_fails_if_server_id_is_empty() {
+ insertServerId("");
+
+ expectEmptyServerIdException();
+
+ test(COMPUTE_ENGINE);
+ }
+
+ @Test
+ @UseDataProvider("allFormatsOfServerId")
+ public void compute_engine_fails_if_server_id_is_invalid(ServerId serverId) {
+ String dbChecksum = "boom";
+ insertServerId(serverId);
+ insertChecksum(dbChecksum);
+ mockChecksumOf(serverId, CHECKSUM_1);
+
+ try {
+ test(SERVER);
+ fail("An ISE should have been raised");
+ }
+ catch (IllegalStateException e) {
+ assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
+ // no changes
+ verifyDb(serverId, dbChecksum);
+ }
+ }
+
+ @DataProvider
+ public static Object[][] allFormatsOfServerId() {
+ return new Object[][] {
+ {OLD_FORMAT_SERVER_ID},
+ {NO_DATABASE_ID_SERVER_ID},
+ {WITH_DATABASE_ID_SERVER_ID}
+ };
+ }
+
+ private void expectEmptyServerIdException() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.core.id is empty in database");
+ }
+
+ private void expectMissingServerIdException() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.core.id is missing in database");
+ }
+
+ private void verifyDb(ServerId expectedServerId, String expectedChecksum) {
+ assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
+ .extracting(PropertyDto::getValue)
+ .containsExactly(expectedServerId.toString());
+ assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM))
+ .hasValue(expectedChecksum);
+ }
+
+ private void mockCreateNewServerId(ServerId newServerId) {
+ when(serverIdFactory.create()).thenReturn(newServerId);
+ when(serverIdFactory.create(any())).thenThrow(new IllegalStateException("new ServerId should not be created from current server id"));
+ }
+
+ private void mockCreateNewServerIdFrom(ServerId currentServerId, ServerId newServerId) {
+ when(serverIdFactory.create()).thenThrow(new IllegalStateException("new ServerId should be created from current server id"));
+ when(serverIdFactory.create(eq(currentServerId))).thenReturn(newServerId);
+ }
+
+ private void verifyCreateNewServerIdFromScratch() {
+ verify(serverIdFactory).create();
+ }
+
+ private void verifyCreateNewServerIdFrom(ServerId currentServerId) {
+ verify(serverIdFactory).create(currentServerId);
+ }
+
+ private void mockChecksumOf(ServerId serverId, String checksum1) {
+ when(serverIdChecksum.computeFor(serverId.toString())).thenReturn(checksum1);
+ }
+
+ private void insertServerId(ServerId serverId) {
+ insertServerId(serverId.toString());
+ }
+
+ private void insertServerId(String serverId) {
+ dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(serverId.toString()));
+ 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(serverIdChecksum, serverIdFactory, dbClient, SonarRuntimeImpl.forSonarQube(Version.create(6, 7), side), webServer);
+ underTest.start();
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ServerId.java b/sonar-core/src/main/java/org/sonar/core/platform/ServerId.java
new file mode 100644
index 00000000000..b39ffdd85c9
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ServerId.java
@@ -0,0 +1,164 @@
+/*
+ * 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.core.platform;
+
+import com.google.common.collect.ImmutableSet;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.CoreProperties;
+import org.sonar.core.util.UuidFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.core.platform.ServerId.Format.DEPRECATED;
+import static org.sonar.core.platform.ServerId.Format.NO_DATABASE_ID;
+import static org.sonar.core.platform.ServerId.Format.WITH_DATABASE_ID;
+
+@Immutable
+public final class ServerId {
+
+ public static final char SPLIT_CHARACTER = '-';
+ public static final int DATABASE_ID_LENGTH = 8;
+ public static final int DEPRECATED_SERVER_ID_LENGTH = 14;
+ public static final int NOT_UUID_DATASET_ID_LENGTH = 15;
+ public static final int UUID_DATASET_ID_LENGTH = 20;
+ private static final Set<Integer> ALLOWED_LENGTHS = ImmutableSet.of(
+ DEPRECATED_SERVER_ID_LENGTH,
+ NOT_UUID_DATASET_ID_LENGTH,
+ NOT_UUID_DATASET_ID_LENGTH + 1 + DATABASE_ID_LENGTH,
+ UUID_DATASET_ID_LENGTH,
+ UUID_DATASET_ID_LENGTH + 1 + DATABASE_ID_LENGTH);
+
+ public enum Format {
+ /* server id format before 6.1 (see SONAR-6992) */
+ DEPRECATED,
+ /* server id format before 6.7.5 and 7.3 (see LICENSE-96) */
+ NO_DATABASE_ID,
+ WITH_DATABASE_ID
+ }
+
+ private final String databaseId;
+ private final String datasetId;
+ private final Format format;
+
+ private ServerId(@Nullable String databaseId, String datasetId) {
+ this.databaseId = databaseId;
+ this.datasetId = datasetId;
+ this.format = computeFormat(databaseId, datasetId);
+ }
+
+ public Optional<String> getDatabaseId() {
+ return Optional.ofNullable(databaseId);
+ }
+
+ public String getDatasetId() {
+ return datasetId;
+ }
+
+ public Format getFormat() {
+ return format;
+ }
+
+ private static Format computeFormat(@Nullable String databaseId, String datasetId) {
+ if (databaseId != null) {
+ return WITH_DATABASE_ID;
+ }
+ if (isDate(datasetId)) {
+ return DEPRECATED;
+ }
+ return NO_DATABASE_ID;
+ }
+
+ public static ServerId parse(String serverId) {
+ String trimmed = serverId.trim();
+
+ int length = trimmed.length();
+ checkArgument(length > 0, "serverId can't be empty");
+ checkArgument(ALLOWED_LENGTHS.contains(length), "serverId does not have a supported length");
+ if (length == DEPRECATED_SERVER_ID_LENGTH || length == UUID_DATASET_ID_LENGTH || length == NOT_UUID_DATASET_ID_LENGTH) {
+ return new ServerId(null, trimmed);
+ }
+
+ int splitCharIndex = trimmed.indexOf(SPLIT_CHARACTER);
+ if (splitCharIndex == -1) {
+ return new ServerId(null, trimmed);
+ }
+ checkArgument(splitCharIndex == DATABASE_ID_LENGTH, "Unrecognized serverId format. Parts have wrong length");
+ return of(trimmed.substring(0, splitCharIndex), trimmed.substring(splitCharIndex + 1));
+ }
+
+ public static ServerId of(@Nullable String databaseId, String datasetId) {
+ if (databaseId != null) {
+ int databaseIdLength = databaseId.length();
+ checkArgument(databaseIdLength == DATABASE_ID_LENGTH, "Illegal databaseId length (%s)", databaseIdLength);
+ }
+ int datasetIdLength = datasetId.length();
+ checkArgument(datasetIdLength == DEPRECATED_SERVER_ID_LENGTH
+ || datasetIdLength == NOT_UUID_DATASET_ID_LENGTH
+ || datasetIdLength == UUID_DATASET_ID_LENGTH, "Illegal datasetId length (%s)", datasetIdLength);
+ return new ServerId(databaseId, datasetId);
+ }
+
+ public static ServerId create(UuidFactory uuidFactory) {
+ return new ServerId(null, uuidFactory.create());
+ }
+
+ /**
+ * Checks whether the specified value is a date according to the old format of the {@link CoreProperties#SERVER_ID}.
+ */
+ private static boolean isDate(String value) {
+ try {
+ new SimpleDateFormat("yyyyMMddHHmmss").parse(value);
+ return true;
+ } catch (ParseException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (databaseId == null) {
+ return datasetId;
+ }
+ return databaseId + SPLIT_CHARACTER + datasetId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ServerId serverId = (ServerId) o;
+ return Objects.equals(databaseId, serverId.databaseId) &&
+ Objects.equals(datasetId, serverId.datasetId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(databaseId, datasetId);
+ }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ServerIdTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ServerIdTest.java
new file mode 100644
index 00000000000..a9d335ad1b7
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/ServerIdTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.core.platform;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.core.util.UuidFactoryImpl;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.StringUtils.repeat;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.DEPRECATED_SERVER_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.SPLIT_CHARACTER;
+import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.Format.DEPRECATED;
+import static org.sonar.core.platform.ServerId.Format.NO_DATABASE_ID;
+import static org.sonar.core.platform.ServerId.Format.WITH_DATABASE_ID;
+
+@RunWith(DataProviderRunner.class)
+public class ServerIdTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void parse_throws_NPE_if_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+
+ ServerId.parse(null);
+ }
+
+ @Test
+ @UseDataProvider("emptyAfterTrim")
+ public void parse_throws_IAE_if_parameter_is_empty_after_trim(String serverId) {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("serverId can't be empty");
+
+ ServerId.parse(serverId);
+ }
+
+ @DataProvider
+ public static Object[][] emptyAfterTrim() {
+ return new Object[][] {
+ {""},
+ {" "},
+ {" "}
+ };
+ }
+
+ @Test
+ @UseDataProvider("wrongFormatWithDatabaseId")
+ public void parse_throws_IAE_if_split_char_is_at_wrong_position(String emptyDatabaseId) {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Unrecognized serverId format. Parts have wrong length");
+
+ ServerId.parse(emptyDatabaseId);
+ }
+
+ @DataProvider
+ public static Object[][] wrongFormatWithDatabaseId() {
+ String onlySplitChar = repeat(SPLIT_CHARACTER + "", DATABASE_ID_LENGTH);
+ String startWithSplitChar = SPLIT_CHARACTER + randomAlphabetic(DATABASE_ID_LENGTH - 1);
+
+ Stream<String> databaseIds = Stream.of(
+ UuidFactoryImpl.INSTANCE.create(),
+ randomAlphabetic(NOT_UUID_DATASET_ID_LENGTH),
+ randomAlphabetic(UUID_DATASET_ID_LENGTH),
+ repeat(SPLIT_CHARACTER + "", NOT_UUID_DATASET_ID_LENGTH),
+ repeat(SPLIT_CHARACTER + "", UUID_DATASET_ID_LENGTH));
+
+ return databaseIds
+ .flatMap(datasetId -> Stream.of(
+ startWithSplitChar + SPLIT_CHARACTER + datasetId,
+ onlySplitChar + SPLIT_CHARACTER + datasetId,
+ startWithSplitChar + randomAlphabetic(1) + datasetId,
+ onlySplitChar + randomAlphabetic(1) + datasetId))
+ .flatMap(serverId -> Stream.of(
+ serverId,
+ " " + serverId,
+ " " + serverId))
+ .map(t -> new Object[] {t})
+ .toArray(Object[][]::new);
+ }
+
+ @Test
+ public void parse_parses_deprecated_format_serverId() {
+ String deprecated = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+
+ ServerId serverId = ServerId.parse(deprecated);
+
+ assertThat(serverId.getFormat()).isEqualTo(DEPRECATED);
+ assertThat(serverId.getDatasetId()).isEqualTo(deprecated);
+ assertThat(serverId.getDatabaseId()).isEmpty();
+ assertThat(serverId.toString()).isEqualTo(deprecated);
+ }
+
+ @Test
+ @UseDataProvider("validOldFormatServerIds")
+ public void parse_parses_no_databaseId_format_serverId(String noDatabaseId) {
+ ServerId serverId = ServerId.parse(noDatabaseId);
+
+ assertThat(serverId.getFormat()).isEqualTo(NO_DATABASE_ID);
+ assertThat(serverId.getDatasetId()).isEqualTo(noDatabaseId);
+ assertThat(serverId.getDatabaseId()).isEmpty();
+ assertThat(serverId.toString()).isEqualTo(noDatabaseId);
+ }
+
+ @DataProvider
+ public static Object[][] validOldFormatServerIds() {
+ return new Object[][] {
+ {UuidFactoryImpl.INSTANCE.create()},
+ {randomAlphabetic(NOT_UUID_DATASET_ID_LENGTH)},
+ {repeat(SPLIT_CHARACTER + "", NOT_UUID_DATASET_ID_LENGTH)},
+ {randomAlphabetic(UUID_DATASET_ID_LENGTH)},
+ {repeat(SPLIT_CHARACTER + "", UUID_DATASET_ID_LENGTH)}
+ };
+ }
+
+ @Test
+ @UseDataProvider("validServerIdWithDatabaseId")
+ public void parse_parses_serverId_with_database_id(String databaseId, String datasetId) {
+ String rawServerId = databaseId + SPLIT_CHARACTER + datasetId;
+
+ ServerId serverId = ServerId.parse(rawServerId);
+
+ assertThat(serverId.getFormat()).isEqualTo(WITH_DATABASE_ID);
+ assertThat(serverId.getDatasetId()).isEqualTo(datasetId);
+ assertThat(serverId.getDatabaseId()).contains(databaseId);
+ assertThat(serverId.toString()).isEqualTo(rawServerId);
+ }
+
+ @DataProvider
+ public static Object[][] validServerIdWithDatabaseId() {
+ return new Object[][] {
+ {randomAlphabetic(DATABASE_ID_LENGTH), randomAlphabetic(NOT_UUID_DATASET_ID_LENGTH)},
+ {randomAlphabetic(DATABASE_ID_LENGTH), randomAlphabetic(UUID_DATASET_ID_LENGTH)},
+ {randomAlphabetic(DATABASE_ID_LENGTH), repeat(SPLIT_CHARACTER + "", NOT_UUID_DATASET_ID_LENGTH)},
+ {randomAlphabetic(DATABASE_ID_LENGTH), repeat(SPLIT_CHARACTER + "", UUID_DATASET_ID_LENGTH)},
+ {randomAlphabetic(DATABASE_ID_LENGTH), UuidFactoryImpl.INSTANCE.create()},
+ };
+ }
+
+ @Test
+ public void parse_does_not_support_deprecated_server_id_with_database_id() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("serverId does not have a supported length");
+
+ ServerId.parse(randomAlphabetic(DATABASE_ID_LENGTH) + SPLIT_CHARACTER + randomAlphabetic(DEPRECATED_SERVER_ID_LENGTH));
+ }
+
+ @Test
+ public void of_throws_NPE_if_datasetId_is_null() {
+ expectedException.expect(NullPointerException.class);
+
+ ServerId.of(randomAlphabetic(DATABASE_ID_LENGTH), null);
+ }
+
+ @Test
+ public void of_throws_IAE_if_datasetId_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Illegal datasetId length (0)");
+
+ ServerId.of(randomAlphabetic(DATABASE_ID_LENGTH), "");
+ }
+
+ @Test
+ public void of_throws_IAE_if_databaseId_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Illegal databaseId length (0)");
+
+ ServerId.of("", randomAlphabetic(UUID_DATASET_ID_LENGTH));
+ }
+
+ @Test
+ @UseDataProvider("datasetIdSupportedLengths")
+ public void of_accepts_null_databaseId(int datasetIdLength) {
+ String datasetId = randomAlphabetic(datasetIdLength);
+ ServerId serverId = ServerId.of(null, datasetId);
+
+ assertThat(serverId.getDatabaseId()).isEmpty();
+ assertThat(serverId.getDatasetId()).isEqualTo(datasetId);
+ }
+
+ @Test
+ @UseDataProvider("illegalDatabaseIdLengths")
+ public void of_throws_IAE_if_databaseId_length_is_not_8(int illegalDatabaseIdLengths) {
+ String databaseId = randomAlphabetic(illegalDatabaseIdLengths);
+ String datasetId = randomAlphabetic(UUID_DATASET_ID_LENGTH);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Illegal databaseId length (" + illegalDatabaseIdLengths + ")");
+
+ ServerId.of(databaseId, datasetId);
+ }
+
+ @DataProvider
+ public static Object[][] illegalDatabaseIdLengths() {
+ return IntStream.range(1, 8 + new Random().nextInt(5))
+ .filter(i -> i != DATABASE_ID_LENGTH)
+ .mapToObj(i -> new Object[] {i})
+ .toArray(Object[][]::new);
+ }
+
+ @Test
+ @UseDataProvider("illegalDatasetIdLengths")
+ public void of_throws_IAE_if_datasetId_length_is_not_8(int illegalDatasetIdLengths) {
+ String datasetId = randomAlphabetic(illegalDatasetIdLengths);
+ String databaseId = randomAlphabetic(DATABASE_ID_LENGTH);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Illegal datasetId length (" + illegalDatasetIdLengths + ")");
+
+ ServerId.of(databaseId, datasetId);
+ }
+
+ @DataProvider
+ public static Object[][] illegalDatasetIdLengths() {
+ return IntStream.range(1, UUID_DATASET_ID_LENGTH + new Random().nextInt(5))
+ .filter(i -> i != UUID_DATASET_ID_LENGTH)
+ .filter(i -> i != NOT_UUID_DATASET_ID_LENGTH)
+ .filter(i -> i != DEPRECATED_SERVER_ID_LENGTH)
+ .mapToObj(i -> new Object[] {i})
+ .toArray(Object[][]::new);
+ }
+
+ @Test
+ @UseDataProvider("datasetIdSupportedLengths")
+ public void equals_is_based_on_databaseId_and_datasetId(int datasetIdLength) {
+ String databaseId = randomAlphabetic(DATABASE_ID_LENGTH - 1) + 'a';
+ String otherDatabaseId = randomAlphabetic(DATABASE_ID_LENGTH - 1) + 'b';
+ String datasetId = randomAlphabetic(datasetIdLength - 1) + 'a';
+ String otherDatasetId = randomAlphabetic(datasetIdLength - 1) + 'b';
+
+ ServerId newServerId = ServerId.of(databaseId, datasetId);
+ assertThat(newServerId).isEqualTo(newServerId);
+ assertThat(newServerId).isEqualTo(ServerId.of(databaseId, datasetId));
+ assertThat(newServerId).isNotEqualTo(new Object());
+ assertThat(newServerId).isNotEqualTo(null);
+ assertThat(newServerId).isNotEqualTo(ServerId.of(otherDatabaseId, datasetId));
+ assertThat(newServerId).isNotEqualTo(ServerId.of(databaseId, otherDatasetId));
+ assertThat(newServerId).isNotEqualTo(ServerId.of(otherDatabaseId, otherDatasetId));
+
+ ServerId oldServerId = ServerId.parse(datasetId);
+ assertThat(oldServerId).isEqualTo(oldServerId);
+ assertThat(oldServerId).isEqualTo(ServerId.parse(datasetId));
+ assertThat(oldServerId).isNotEqualTo(ServerId.parse(otherDatasetId));
+ assertThat(oldServerId).isNotEqualTo(ServerId.of(databaseId, datasetId));
+ }
+
+ @Test
+ @UseDataProvider("datasetIdSupportedLengths")
+ public void hashcode_is_based_on_databaseId_and_datasetId(int datasetIdLength) {
+ String databaseId = randomAlphabetic(DATABASE_ID_LENGTH - 1) + 'a';
+ String otherDatabaseId = randomAlphabetic(DATABASE_ID_LENGTH - 1) + 'b';
+ String datasetId = randomAlphabetic(datasetIdLength - 1) + 'a';
+ String otherDatasetId = randomAlphabetic(datasetIdLength - 1) + 'b';
+
+ ServerId newServerId = ServerId.of(databaseId, datasetId);
+ assertThat(newServerId.hashCode()).isEqualTo(newServerId.hashCode());
+ assertThat(newServerId.hashCode()).isEqualTo(ServerId.of(databaseId, datasetId).hashCode());
+ assertThat(newServerId.hashCode()).isNotEqualTo(new Object().hashCode());
+ assertThat(newServerId.hashCode()).isNotEqualTo(null);
+ assertThat(newServerId.hashCode()).isNotEqualTo(ServerId.of(otherDatabaseId, datasetId).hashCode());
+ assertThat(newServerId.hashCode()).isNotEqualTo(ServerId.of(databaseId, otherDatasetId).hashCode());
+ assertThat(newServerId.hashCode()).isNotEqualTo(ServerId.of(otherDatabaseId, otherDatasetId).hashCode());
+
+ ServerId oldServerId = ServerId.parse(datasetId);
+ assertThat(oldServerId.hashCode()).isEqualTo(oldServerId.hashCode());
+ assertThat(oldServerId.hashCode()).isEqualTo(ServerId.parse(datasetId).hashCode());
+ assertThat(oldServerId.hashCode()).isNotEqualTo(ServerId.parse(otherDatasetId).hashCode());
+ assertThat(oldServerId.hashCode()).isNotEqualTo(ServerId.of(databaseId, datasetId).hashCode());
+ }
+
+ @DataProvider
+ public static Object[][] datasetIdSupportedLengths() {
+ return new Object[][] {
+ {ServerId.NOT_UUID_DATASET_ID_LENGTH},
+ {UUID_DATASET_ID_LENGTH},
+ };
+ }
+}