@@ -132,7 +132,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.log.ServerLogging; | |||
@@ -145,6 +144,7 @@ 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.property.InternalPropertiesImpl; | |||
@@ -360,7 +360,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, |
@@ -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; | |||
@@ -40,7 +43,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; | |||
@@ -84,7 +86,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)); | |||
@@ -110,6 +112,7 @@ public class ComputeEngineContainerImplTest { | |||
assertThat(picoContainer.getParent().getComponentAdapters()).hasSize( | |||
CONTAINER_ITSELF | |||
+ 7 // level 3 | |||
+ 4 // content of ServerIdModule | |||
); | |||
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize( | |||
CONTAINER_ITSELF | |||
@@ -140,6 +143,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(); |
@@ -26,11 +26,11 @@ import org.sonar.core.util.DefaultHttpDownloader; | |||
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; | |||
@@ -47,7 +47,7 @@ public class PlatformLevel3 extends PlatformLevel { | |||
addIfStartupLeader(StartupMetadataPersister.class); | |||
add( | |||
NoopDatabaseMigrationImpl.class, | |||
ServerIdManager.class, | |||
ServerIdModule.class, | |||
ServerImpl.class, | |||
DatabaseSettingLoader.class, | |||
DatabaseSettingsEnabler.class, |
@@ -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); |
@@ -0,0 +1,42 @@ | |||
/* | |||
* 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)); | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
/* | |||
* 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); | |||
} |
@@ -0,0 +1,69 @@ | |||
/* | |||
* 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"); | |||
} | |||
} |
@@ -17,56 +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.process.ProcessProperties.Property.JDBC_URL; | |||
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); | |||
@@ -74,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(JDBC_URL.getKey()).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) { | |||
@@ -144,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 |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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 | |||
); | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* 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; |
@@ -1,258 +0,0 @@ | |||
/* | |||
* 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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -0,0 +1,119 @@ | |||
/* | |||
* 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"); | |||
} | |||
} |
@@ -0,0 +1,361 @@ | |||
/* | |||
* 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(); | |||
} | |||
} |
@@ -0,0 +1,164 @@ | |||
/* | |||
* 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); | |||
} | |||
} |
@@ -0,0 +1,309 @@ | |||
/* | |||
* 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}, | |||
}; | |||
} | |||
} |