Browse Source

LICENSE-96 implement support for staging and new server id format

tags/7.5
Sébastien Lesaint 6 years ago
parent
commit
45f6d410d3
17 changed files with 1307 additions and 348 deletions
  1. 2
    2
      server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
  2. 9
    2
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  3. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
  4. 3
    15
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/JdbcUrlSanitizer.java
  5. 42
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdChecksum.java
  6. 34
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactory.java
  7. 69
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactoryImpl.java
  8. 64
    55
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdManager.java
  9. 35
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdModule.java
  10. 23
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/serverid/package-info.java
  11. 0
    258
      server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java
  12. 7
    14
      server/sonar-server/src/test/java/org/sonar/server/platform/serverid/JdbcUrlSanitizerTest.java
  13. 64
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdChecksumTest.java
  14. 119
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdFactoryImplTest.java
  15. 361
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java
  16. 164
    0
      sonar-core/src/main/java/org/sonar/core/platform/ServerId.java
  17. 309
    0
      sonar-core/src/test/java/org/sonar/core/platform/ServerIdTest.java

+ 2
- 2
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java View File

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

+ 9
- 2
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

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

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java View File

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

server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdChecksum.java → server/sonar-server/src/main/java/org/sonar/server/platform/serverid/JdbcUrlSanitizer.java View File

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

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

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

}

+ 34
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactory.java View File

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

+ 69
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdFactoryImpl.java View File

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

}

server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdManager.java → server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdManager.java View File

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

+ 35
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/serverid/ServerIdModule.java View File

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

);
}
}

+ 23
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/serverid/package-info.java View File

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

+ 0
- 258
server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdManagerTest.java View File

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

server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdChecksumTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/serverid/JdbcUrlSanitizerTest.java View File

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


}

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

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

+ 119
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdFactoryImplTest.java View File

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

+ 361
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java View File

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

+ 164
- 0
sonar-core/src/main/java/org/sonar/core/platform/ServerId.java View File

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

+ 309
- 0
sonar-core/src/test/java/org/sonar/core/platform/ServerIdTest.java View File

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

Loading…
Cancel
Save