import org.sonar.server.platform.DatabaseServerCompatibility;
import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.ServerFileSystemImpl;
+import org.sonar.server.platform.ServerIdManager;
import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.ServerLifecycleNotifier;
import org.sonar.server.platform.ServerLogging;
private static Object[] level3Components() {
return new Object[] {
new StartupMetadataProvider(),
+ ServerIdManager.class,
UriReader.class,
ServerImpl.class
};
);
assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
- + 3 // level 3
+ + 4 // level 3
);
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import javax.annotation.Nullable;
+import org.picocontainer.Startable;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.cluster.Cluster;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.sonar.api.CoreProperties.SERVER_ID;
+
+public class ServerIdManager implements Startable {
+ private final DbClient dbClient;
+ private final SonarRuntime runtime;
+ private final Cluster cluster;
+ private final UuidFactory uuidFactory;
+
+ public ServerIdManager(DbClient dbClient, SonarRuntime runtime, Cluster cluster, UuidFactory uuidFactory) {
+ this.dbClient = dbClient;
+ this.runtime = runtime;
+ this.cluster = cluster;
+ this.uuidFactory = uuidFactory;
+ }
+
+ @Override
+ public void start() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ PropertyDto dto = dbClient.propertiesDao().selectGlobalProperty(dbSession, SERVER_ID);
+ if (runtime.getSonarQubeSide() == SonarQubeSide.SERVER && cluster.isStartupLeader()) {
+ persistServerIdIfMissingOrOldFormatted(dbSession, dto);
+ } else {
+ ensureServerIdIsSet(dto);
+ }
+ }
+ }
+
+ /**
+ * Insert or update {@link CoreProperties#SERVER_ID} property in DB to a UUID if it doesn't exist or if it's a date
+ * (per the old format of {@link CoreProperties#SERVER_ID} before 6.1).
+ */
+ private void persistServerIdIfMissingOrOldFormatted(DbSession dbSession, @Nullable PropertyDto dto) {
+ if (dto == null || dto.getValue().isEmpty() || isDate(dto.getValue())) {
+ dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(SERVER_ID).setValue(uuidFactory.create()));
+ dbSession.commit();
+ }
+ }
+
+ /**
+ * 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 static void ensureServerIdIsSet(@Nullable PropertyDto dto) {
+ checkState(dto != null, "Property %s is missing in database", SERVER_ID);
+ checkState(!isBlank(dto.getValue()), "Property %s is set but empty in database", SERVER_ID);
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+}
@Override
public String getId() {
- return state.getStartupId();
+ return settings.getString(CoreProperties.SERVER_ID);
}
@Override
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.server.ServerSide;
-import static java.util.Objects.requireNonNull;
-
@ComputeEngineSide
@ServerSide
@Immutable
public class StartupMetadata {
- private final String startupId;
private final long startedAt;
- public StartupMetadata(String startupId, long startedAt) {
- this.startupId = requireNonNull(startupId);
+ public StartupMetadata(long startedAt) {
this.startedAt = startedAt;
}
- public String getStartupId() {
- return startupId;
- }
-
public long getStartedAt() {
return startedAt;
}
@Override
public void start() {
String startedAt = DateUtils.formatDateTime(new Date(metadata.getStartedAt()));
- save(CoreProperties.SERVER_ID, metadata.getStartupId());
save(CoreProperties.SERVER_STARTTIME, startedAt);
}
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.property.PropertyDto;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.sonar.api.CoreProperties.SERVER_STARTTIME;
@ComputeEngineSide
@ServerSide
private StartupMetadata cache = null;
- public StartupMetadata provide(UuidFactory uuidFactory, System2 system, SonarRuntime runtime, Cluster cluster, DbClient dbClient) {
+ public StartupMetadata provide(System2 system, SonarRuntime runtime, Cluster cluster, DbClient dbClient) {
if (cache == null) {
if (runtime.getSonarQubeSide() == SonarQubeSide.SERVER && cluster.isStartupLeader()) {
- cache = generate(uuidFactory, system);
+ cache = generate(system);
} else {
cache = load(dbClient);
}
}
/**
- * Generate a UUID. It is not persisted yet as db structure may not be up-to-date if migrations
- * have to be executed. This is done later by {@link StartupMetadataPersister}
+ * Generate {@link CoreProperties#SERVER_ID} if it doesn't exist yet, otherwise just load it from DB, and always
+ * generate a {@link CoreProperties#SERVER_STARTTIME}.
+ * <p>
+ * Persistence is performed by {@link StartupMetadataPersister}.
+ * </p>
*/
- private static StartupMetadata generate(UuidFactory uuidFactory, System2 system) {
- String startupId = uuidFactory.create();
- long startedAt = system.now();
- return new StartupMetadata(startupId, startedAt);
+ private static StartupMetadata generate(System2 system) {
+ return new StartupMetadata(system.now());
}
/**
*/
private static StartupMetadata load(DbClient dbClient) {
try (DbSession dbSession = dbClient.openSession(false)) {
- String startupId = selectProperty(dbClient, dbSession, CoreProperties.SERVER_ID);
- String startedAt = selectProperty(dbClient, dbSession, CoreProperties.SERVER_STARTTIME);
- return new StartupMetadata(startupId, DateUtils.parseDateTime(startedAt).getTime());
+ String startedAt = selectProperty(dbClient, dbSession, SERVER_STARTTIME);
+ return new StartupMetadata(DateUtils.parseDateTime(startedAt).getTime());
}
}
import org.sonar.db.charset.DatabaseCharsetChecker;
import org.sonar.db.version.MigrationStepModule;
import org.sonar.server.platform.DefaultServerUpgradeStatus;
-import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.StartupMetadataProvider;
import org.sonar.server.platform.db.CheckDatabaseCharsetAtStartup;
import org.sonar.server.platform.db.migrations.DatabaseMigrator;
protected void configureLevel() {
add(
new StartupMetadataProvider(),
- ServerImpl.class,
DefaultServerUpgradeStatus.class,
Durations.class,
JRubyI18n.class,
DefaultI18n.class,
RuleI18nManager.class,
-
// DB migration
PlatformDatabaseMigrationExecutorServiceImpl.class,
PlatformDatabaseMigration.class,
import org.sonar.api.utils.UriReader;
import org.sonar.server.platform.ServerIdGenerator;
import org.sonar.server.platform.ServerIdLoader;
+import org.sonar.server.platform.ServerIdManager;
+import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.StartupMetadataPersister;
import org.sonar.server.setting.DatabaseSettingLoader;
import org.sonar.server.setting.DatabaseSettingsEnabler;
protected void configureLevel() {
addIfStartupLeader(StartupMetadataPersister.class);
add(
+ ServerIdManager.class,
+ ServerImpl.class,
DatabaseSettingLoader.class,
DatabaseSettingsEnabler.class,
UriReader.class,
*/
package org.sonar.server.platform.platformlevel;
+import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.ws.DbMigrationStatusAction;
import org.sonar.server.platform.ws.MigrateDbAction;
import org.sonar.server.platform.ws.StatusAction;
@Override
protected void configureLevel() {
add(
+ ServerImpl.class,
// Server WS
StatusAction.class,
MigrateDbAction.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.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.platform.cluster.ClusterMock;
+
+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 Version SOME_VERSION = Version.create(5, 6);
+ private static final String SOME_UUID = "some uuid";
+
+ @Rule
+ public final DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private DbClient dbClient = dbTester.getDbClient();
+ private DbSession dbSession = dbTester.getSession();
+ private ClusterMock cluster = new ClusterMock();
+ private UuidFactory uuidFactory = mock(UuidFactory.class);
+
+ private static SonarRuntime runtimeFor(SonarQubeSide side) {
+ return SonarRuntimeImpl.forSonarQube(SOME_VERSION, side);
+ }
+
+ @Test
+ public void start_persists_new_serverId_if_server_startupLeader_and_serverId_does_not_exist() {
+ when(uuidFactory.create()).thenReturn(SOME_UUID);
+ cluster.setStartupLeader(true);
+
+ new ServerIdManager(dbClient, runtimeFor(SERVER), cluster, uuidFactory)
+ .start();
+
+ assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
+ .extracting(PropertyDto::getValue)
+ .containsOnly(SOME_UUID);
+ }
+
+ @Test
+ public void start_persists_new_serverId_if_server_startupLeader_and_serverId_is_an_old_date() {
+ insertPropertyCoreId("20161123150657");
+ when(uuidFactory.create()).thenReturn(SOME_UUID);
+ cluster.setStartupLeader(true);
+
+ new ServerIdManager(dbClient, runtimeFor(SERVER), cluster, uuidFactory)
+ .start();
+
+ assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
+ .extracting(PropertyDto::getValue)
+ .containsOnly(SOME_UUID);
+ }
+
+ private void insertPropertyCoreId(String value) {
+ dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(value));
+ dbSession.commit();
+ }
+
+ @Test
+ public void start_persists_new_serverId_if_server_startupLeader_and_serverId_is_empty() {
+ insertPropertyCoreId("");
+ when(uuidFactory.create()).thenReturn(SOME_UUID);
+ cluster.setStartupLeader(true);
+
+ new ServerIdManager(dbClient, runtimeFor(SERVER), cluster, uuidFactory)
+ .start();
+
+ assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
+ .extracting(PropertyDto::getValue)
+ .containsOnly(SOME_UUID);
+ }
+
+ @Test
+ public void start_fails_with_ISE_if_serverId_is_null_and_server_is_not_startupLeader() {
+ cluster.setStartupLeader(false);
+
+ ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(SERVER), cluster, uuidFactory);
+
+ expectMissingCoreIdException();
+
+ underTest.start();
+ }
+
+ @Test
+ public void start_fails_with_ISE_if_serverId_is_empty_and_server_is_not_startupLeader() {
+ insertPropertyCoreId("");
+ cluster.setStartupLeader(false);
+
+ ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(SERVER), cluster, uuidFactory);
+
+ expectEmptyCoreIdException();
+
+ underTest.start();
+ }
+
+ @Test
+ public void start_fails_with_ISE_if_serverId_is_null_and_not_server() {
+ cluster.setStartupLeader(false);
+
+ ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(COMPUTE_ENGINE), cluster, uuidFactory);
+
+ expectMissingCoreIdException();
+
+ underTest.start();
+ }
+
+ @Test
+ public void start_fails_with_ISE_if_serverId_is_empty_and_not_server() {
+ insertPropertyCoreId("");
+
+ ServerIdManager underTest = new ServerIdManager(dbClient, runtimeFor(COMPUTE_ENGINE), cluster, uuidFactory);
+
+ expectEmptyCoreIdException();
+
+ underTest.start();
+ }
+
+ @Test
+ public void start_does_not_fail_if_serverId_exists_and_server_is_not_startupLeader() {
+ insertPropertyCoreId(SOME_UUID);
+ cluster.setStartupLeader(false);
+
+ new ServerIdManager(dbClient, runtimeFor(SERVER), cluster, uuidFactory).start();
+ }
+
+ @Test
+ public void start_does_not_fail_if_serverId_exists_and_not_server() {
+ insertPropertyCoreId(SOME_UUID);
+
+ new ServerIdManager(dbClient, runtimeFor(COMPUTE_ENGINE), cluster, uuidFactory).start();
+ }
+
+ private void expectEmptyCoreIdException() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.core.id is set but empty in database");
+ }
+
+ private void expectMissingCoreIdException() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.core.id is missing in database");
+ }
+}
public void test_startup_information() throws IOException {
long time = 123_456_789L;
when(state.getStartedAt()).thenReturn(time);
- when(state.getStartupId()).thenReturn("an_id");
assertThat(underTest.getStartedAt().getTime()).isEqualTo(time);
+ }
+
+ @Test
+ public void test_id() throws IOException {
+ settings.setProperty(CoreProperties.SERVER_ID, "an_id");
+
assertThat(underTest.getId()).isEqualTo("an_id");
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
-
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
- private StartupMetadata metadata = new StartupMetadata("an_id", 123_456_789L);
+ private StartupMetadata metadata = new StartupMetadata(123_456_789L);
private StartupMetadataPersister underTest = new StartupMetadataPersister(metadata, dbTester.getDbClient());
@Test
public void persist_metadata_at_startup() {
underTest.start();
- assertPersistedProperty(CoreProperties.SERVER_ID, metadata.getStartupId());
assertPersistedProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(metadata.getStartedAt()));
underTest.stop();
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.Version;
-import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.property.PropertyDto;
import org.sonar.server.platform.cluster.ClusterMock;
public class StartupMetadataProviderTest {
private static final long A_DATE = 1_500_000_000_000L;
- private static final String AN_ID = "generated_id";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
- private UuidFactory uuidFactory = mock(UuidFactory.class);
private StartupMetadataProvider underTest = new StartupMetadataProvider();
private System2 system = mock(System2.class);
private ClusterMock cluster = new ClusterMock();
@Test
- public void generate_and_do_not_persist_metadata_if_server_is_startup_leader() {
- when(uuidFactory.create()).thenReturn(AN_ID);
+ public void generate_SERVER_STARTIME_but_do_not_persist_it_if_server_is_startup_leader() {
when(system.now()).thenReturn(A_DATE);
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER);
cluster.setStartupLeader(true);
- StartupMetadata metadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ StartupMetadata metadata = underTest.provide(system, runtime, cluster, dbTester.getDbClient());
assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
- assertThat(metadata.getStartupId()).isEqualTo(AN_ID);
- assertNotPersistedProperty(CoreProperties.SERVER_ID);
assertNotPersistedProperty(CoreProperties.SERVER_STARTTIME);
// keep a cache
- StartupMetadata secondMetadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ StartupMetadata secondMetadata = underTest.provide(system, runtime, cluster, dbTester.getDbClient());
assertThat(secondMetadata).isSameAs(metadata);
}
cluster.setStartupLeader(false);
expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Property sonar.core.id is missing in database");
- underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ expectedException.expectMessage("Property sonar.core.startTime is missing in database");
+ underTest.provide(system, runtime, cluster, dbTester.getDbClient());
}
private void testLoadingFromDatabase(SonarRuntime runtime, boolean isStartupLeader) {
- new StartupMetadataPersister(new StartupMetadata(AN_ID, A_DATE), dbTester.getDbClient()).start();
+ new StartupMetadataPersister(new StartupMetadata(A_DATE), dbTester.getDbClient()).start();
cluster.setStartupLeader(isStartupLeader);
- StartupMetadata metadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ StartupMetadata metadata = underTest.provide(system, runtime, cluster, dbTester.getDbClient());
assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
- assertThat(metadata.getStartupId()).isEqualTo(AN_ID);
// still in database
- assertPersistedProperty(CoreProperties.SERVER_ID, AN_ID);
assertPersistedProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(A_DATE));
// keep a cache
- StartupMetadata secondMetadata = underTest.provide(uuidFactory, system, runtime, cluster, dbTester.getDbClient());
+ StartupMetadata secondMetadata = underTest.provide(system, runtime, cluster, dbTester.getDbClient());
assertThat(secondMetadata).isSameAs(metadata);
- verifyZeroInteractions(uuidFactory, system);
+ verifyZeroInteractions(system);
}
private void assertPersistedProperty(String propertyKey, String expectedValue) {