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.log.ServerLogging;
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.property.InternalPropertiesImpl;
private static void populateLevel3(ComponentContainer container) {
container.add(
new StartupMetadataProvider(),
- ServerIdManager.class,
+ ServerIdModule.class,
UriReader.class,
ServerImpl.class,
DefaultOrganizationProviderImpl.class,
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;
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;
// 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));
assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
+ 7 // level 3
+ + 4 // content of ServerIdModule
);
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
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();
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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();
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 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.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.isEmpty;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.sonar.api.CoreProperties.SERVER_ID;
-import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
-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(Configuration config, DbClient dbClient, SonarRuntime runtime, WebServer webServer, UuidFactory uuidFactory) {
- this.config = config;
- 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);
- dbSession.commit();
- } else {
- ensureServerIdIsValid(dbSession);
- }
- }
- }
-
- 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));
- }
-
- /**
- * 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 String computeChecksum(String serverId) {
- String jdbcUrl = config.get(JDBC_URL.getKey()).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
- public void stop() {
- // nothing to do
- }
-}
import org.sonar.server.async.AsyncExecutionModule;
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.WebCoreExtensionsInstaller;
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;
addIfStartupLeader(StartupMetadataPersister.class);
add(
NoopDatabaseMigrationImpl.class,
- ServerIdManager.class,
+ ServerIdModule.class,
ServerImpl.class,
DatabaseSettingLoader.class,
DatabaseSettingsEnabler.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.KeyValueFormat;
+
+public class JdbcUrlSanitizer {
+ private static final String SQLSERVER_PREFIX = "jdbc:sqlserver://";
+
+ public String sanitize(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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Property.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.getKey()).orElseThrow(() -> new IllegalStateException("Missing JDBC URL"));
+ return DigestUtils.sha256Hex(serverId + "|" + jdbcUrlSanitizer.sanitize(jdbcUrl));
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Property.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.getKey()).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");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 java.util.Optional;
+import org.picocontainer.Startable;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+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 ServerIdChecksum serverIdChecksum;
+ private final ServerIdFactory serverIdFactory;
+ private final DbClient dbClient;
+ private final SonarRuntime runtime;
+ private final WebServer webServer;
+
+ 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;
+ }
+
+ @Override
+ public void start() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ if (runtime.getSonarQubeSide() == SonarQubeSide.SERVER && webServer.isStartupLeader()) {
+ 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);
+ }
+ }
+ }
+
+ private ServerId keepOrReplaceCurrentServerId(DbSession dbSession, ServerId currentServerId, Optional<String> checksum) {
+ if (keepServerId(currentServerId, checksum)) {
+ return currentServerId;
+ }
+
+ ServerId serverId = replaceCurrentServerId(currentServerId);
+ persistServerId(dbSession, serverId);
+ return serverId;
+ }
+
+ 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 false;
+ }
+
+ if (checksum.isPresent()) {
+ 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 false;
+ }
+ }
+
+ // Existing server ID must be kept when upgrading to 6.7+. In that case the checksum does not exist.
+ return true;
+ }
+
+ private ServerId replaceCurrentServerId(ServerId currentServerId) {
+ if (currentServerId.getFormat() == DEPRECATED) {
+ return serverIdFactory.create();
+ }
+ return serverIdFactory.create(currentServerId);
+ }
+
+ 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.
+ String checksum = serverIdChecksum.computeFor(serverId.toString());
+ persistChecksum(dbSession, checksum);
+ }
+
+ private void persistServerId(DbSession dbSession, ServerId serverId) {
+ dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(SERVER_ID).setValue(serverId.toString()));
+ }
+
+ private void persistChecksum(DbSession dbSession, String checksump) {
+ dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, checksump);
+ }
+
+ 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(serverIdChecksum.computeFor(id.getValue())), "Server ID is invalid");
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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
+
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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();
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JdbcUrlSanitizerTest {
+ private JdbcUrlSanitizer underTest = new JdbcUrlSanitizer();
+
+ @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 void verifyJdbcUrl(String url, String expectedResult) {
+ assertThat(underTest.sanitize(url)).isEqualTo(expectedResult);
+ }
+
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Property.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.getKey(), jdbcUrl);
+ return underTest.computeFor(serverId);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Property.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.getKey(), 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.getKey(), 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");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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},
+ };
+ }
+}