Browse Source

SONAR-22142 improve logging of db-migration steps with progress information

pull/3361/head
Steve Marion 1 month ago
parent
commit
aedbebebed

+ 32
- 11
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java View File

import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.sonar.core.platform.Container; import org.sonar.core.platform.Container;
import org.sonar.core.util.logs.Profiler; import org.sonar.core.util.logs.Profiler;
import org.sonar.server.platform.db.migration.MutableDatabaseMigrationState;
import org.sonar.server.platform.db.migration.history.MigrationHistory; import org.sonar.server.platform.db.migration.history.MigrationHistory;


import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;


public class MigrationStepsExecutorImpl implements MigrationStepsExecutor { public class MigrationStepsExecutorImpl implements MigrationStepsExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger("DbMigrations"); private static final Logger LOGGER = LoggerFactory.getLogger("DbMigrations");
private static final String GLOBAL_START_MESSAGE = "Executing DB migrations...";
private static final String GLOBAL_END_MESSAGE = "Executed DB migrations: {}";
private static final String STEP_START_PATTERN = "{}...";
private static final String STEP_STOP_PATTERN = "{}: {}";
private static final String GLOBAL_START_MESSAGE = "Executing {} DB migrations...";
private static final String GLOBAL_END_MESSAGE = "Executed {}/{} DB migrations: {}";
private static final String STEP_START_PATTERN = "{}/{} {}...";
private static final String STEP_STOP_PATTERN = "{}/{} {}: {}";


private final Container migrationContainer; private final Container migrationContainer;
private final MigrationHistory migrationHistory; private final MigrationHistory migrationHistory;
private final MutableDatabaseMigrationState databaseMigrationState;


public MigrationStepsExecutorImpl(Container migrationContainer, MigrationHistory migrationHistory) {
public MigrationStepsExecutorImpl(
Container migrationContainer, MigrationHistory migrationHistory, MutableDatabaseMigrationState databaseMigrationState) {
this.migrationContainer = migrationContainer; this.migrationContainer = migrationContainer;
this.migrationHistory = migrationHistory; this.migrationHistory = migrationHistory;
this.databaseMigrationState = databaseMigrationState;
} }


@Override @Override
public void execute(List<RegisteredMigrationStep> steps, MigrationStatusListener listener) { public void execute(List<RegisteredMigrationStep> steps, MigrationStatusListener listener) {
Profiler globalProfiler = Profiler.create(LOGGER); Profiler globalProfiler = Profiler.create(LOGGER);
globalProfiler.startInfo(GLOBAL_START_MESSAGE);
globalProfiler.startInfo(GLOBAL_START_MESSAGE, databaseMigrationState.getTotalMigrations());
boolean allStepsExecuted = false; boolean allStepsExecuted = false;
try { try {
for (RegisteredMigrationStep step : steps) { for (RegisteredMigrationStep step : steps) {
allStepsExecuted = true; allStepsExecuted = true;
} finally { } finally {
if (allStepsExecuted) { if (allStepsExecuted) {
globalProfiler.stopInfo(GLOBAL_END_MESSAGE, "success");
globalProfiler.stopInfo(GLOBAL_END_MESSAGE,
databaseMigrationState.getCompletedMigrations(),
databaseMigrationState.getTotalMigrations(),
"success");
} else { } else {
globalProfiler.stopError(GLOBAL_END_MESSAGE, "failure");
globalProfiler.stopError(GLOBAL_END_MESSAGE,
databaseMigrationState.getCompletedMigrations(),
databaseMigrationState.getTotalMigrations(),
"failure");
} }
} }
} }


private void execute(RegisteredMigrationStep step, MigrationStep migrationStep) { private void execute(RegisteredMigrationStep step, MigrationStep migrationStep) {
Profiler stepProfiler = Profiler.create(LOGGER); Profiler stepProfiler = Profiler.create(LOGGER);
stepProfiler.startInfo(STEP_START_PATTERN, step);
stepProfiler.startInfo(STEP_START_PATTERN,
databaseMigrationState.getCompletedMigrations() + 1,
databaseMigrationState.getTotalMigrations(),
step);
boolean done = false; boolean done = false;
try { try {
migrationStep.execute(); migrationStep.execute();
throw new MigrationStepExecutionException(step, e); throw new MigrationStepExecutionException(step, e);
} finally { } finally {
if (done) { if (done) {
stepProfiler.stopInfo(STEP_STOP_PATTERN, step, "success");
stepProfiler.stopInfo(STEP_STOP_PATTERN,
databaseMigrationState.getCompletedMigrations() + 1,
databaseMigrationState.getTotalMigrations(),
step,
"success");
} else { } else {
stepProfiler.stopError(STEP_STOP_PATTERN, step, "failure");
stepProfiler.stopError(STEP_STOP_PATTERN,
databaseMigrationState.getCompletedMigrations() + 1,
databaseMigrationState.getTotalMigrations(),
step,
"failure");
} }
} }
} }

+ 2
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImplTest.java View File



import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.sonar.server.platform.db.migration.MutableDatabaseMigrationState;
import org.sonar.server.platform.db.migration.history.MigrationHistory; import org.sonar.server.platform.db.migration.history.MigrationHistory;
import org.sonar.server.platform.db.migration.step.MigrationStep; import org.sonar.server.platform.db.migration.step.MigrationStep;
import org.sonar.server.platform.db.migration.step.MigrationSteps; import org.sonar.server.platform.db.migration.step.MigrationSteps;


// add MigrationStepsExecutorImpl's dependencies // add MigrationStepsExecutorImpl's dependencies
migrationContainer.add(mock(MigrationHistory.class)); migrationContainer.add(mock(MigrationHistory.class));
migrationContainer.add(mock(MutableDatabaseMigrationState.class));


migrationContainer.startComponents(); migrationContainer.startComponents();
underTest.populateContainer(migrationContainer); underTest.populateContainer(migrationContainer);

+ 6
- 4
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java View File

import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.sonar.core.platform.SpringComponentContainer; import org.sonar.core.platform.SpringComponentContainer;
import org.sonar.server.platform.db.migration.MutableDatabaseMigrationState;
import org.sonar.server.platform.db.migration.history.MigrationHistory; import org.sonar.server.platform.db.migration.history.MigrationHistory;
import org.sonar.server.platform.db.migration.step.MigrationStep; import org.sonar.server.platform.db.migration.step.MigrationStep;
import org.sonar.server.platform.db.migration.step.MigrationSteps; import org.sonar.server.platform.db.migration.step.MigrationSteps;


class MigrationEngineImplTest { class MigrationEngineImplTest {
private final MigrationHistory migrationHistory = mock(MigrationHistory.class); private final MigrationHistory migrationHistory = mock(MigrationHistory.class);
private final MutableDatabaseMigrationState databaseMigrationState = mock();
private final SpringComponentContainer serverContainer = new SpringComponentContainer(); private final SpringComponentContainer serverContainer = new SpringComponentContainer();
private final MigrationSteps migrationSteps = mock(MigrationSteps.class); private final MigrationSteps migrationSteps = mock(MigrationSteps.class);
private final StepRegistry stepRegistry = new StepRegistry(); private final StepRegistry stepRegistry = new StepRegistry();
serverContainer.add(migrationSteps); serverContainer.add(migrationSteps);
serverContainer.add(migrationHistory); serverContainer.add(migrationHistory);
serverContainer.add(stepRegistry); serverContainer.add(stepRegistry);
serverContainer.add(databaseMigrationState);
serverContainer.startComponents(); serverContainer.startComponents();
} }


private record TestMigrationStep(StepRegistry registry) implements MigrationStep { private record TestMigrationStep(StepRegistry registry) implements MigrationStep {


@Override @Override
public void execute() throws SQLException {
registry.stepRan = true;
}
public void execute() throws SQLException {
registry.stepRan = true;
} }
}
} }

+ 59
- 38
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java View File

import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.event.Level; import org.slf4j.event.Level;
import org.sonar.api.testfixtures.log.LogTester;
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
import org.sonar.core.platform.SpringComponentContainer; import org.sonar.core.platform.SpringComponentContainer;
import org.sonar.server.platform.db.migration.MutableDatabaseMigrationState;
import org.sonar.server.platform.db.migration.engine.MigrationContainer; import org.sonar.server.platform.db.migration.engine.MigrationContainer;
import org.sonar.server.platform.db.migration.engine.SimpleMigrationContainer; import org.sonar.server.platform.db.migration.engine.SimpleMigrationContainer;
import org.sonar.server.platform.db.migration.history.MigrationHistory; import org.sonar.server.platform.db.migration.history.MigrationHistory;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;


public class MigrationStepsExecutorImplTest {
@Rule
public LogTester logTester = new LogTester();
class MigrationStepsExecutorImplTest {

@RegisterExtension
private final LogTesterJUnit5 logTester = new LogTesterJUnit5();


private MigrationContainer migrationContainer = new SimpleMigrationContainer(); private MigrationContainer migrationContainer = new SimpleMigrationContainer();
private MigrationHistory migrationHistor = mock(MigrationHistory.class);
private MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistor);
private NoOpMigrationStatusListener noOpMigrationStatusListener = new NoOpMigrationStatusListener();
private MigrationHistory migrationHistory = mock(MigrationHistory.class);
private MutableDatabaseMigrationState databaseMigrationState = mock();
private MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistory, databaseMigrationState);
private NoOpMigrationStatusListener migrationStatusListener = mock();

@BeforeEach
void setUp() {
when(databaseMigrationState.getTotalMigrations()).thenReturn(5);
when(databaseMigrationState.getCompletedMigrations()).thenReturn(2);
}


@Test @Test
public void execute_does_not_fail_when_stream_is_empty_and_log_start_stop_INFO() {
underTest.execute(Collections.emptyList(), null);
void execute_does_not_fail_when_stream_is_empty_and_log_start_stop_INFO() {
underTest.execute(Collections.emptyList(), migrationStatusListener);


verifyNoInteractions(migrationStatusListener);
assertThat(logTester.logs()).hasSize(2); assertThat(logTester.logs()).hasSize(2);
assertLogLevel(Level.INFO, "Executing DB migrations...", "Executed DB migrations: success | time=");
assertLogLevel(Level.INFO, "Executing 5 DB migrations...", "Executed 2/5 DB migrations: success | time=");
} }


@Test @Test
public void execute_fails_with_ISE_if_no_instance_of_computation_step_exist_in_container() {
void execute_fails_with_ISE_if_no_instance_of_computation_step_exist_in_container() {
List<RegisteredMigrationStep> steps = asList(registeredStepOf(1, MigrationStep1.class)); List<RegisteredMigrationStep> steps = asList(registeredStepOf(1, MigrationStep1.class));


((SpringComponentContainer) migrationContainer).startComponents(); ((SpringComponentContainer) migrationContainer).startComponents();
try { try {
underTest.execute(steps, noOpMigrationStatusListener);
underTest.execute(steps, migrationStatusListener);
fail("execute should have thrown a IllegalStateException"); fail("execute should have thrown a IllegalStateException");
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
assertThat(e).hasMessage("Unable to load component " + MigrationStep1.class); assertThat(e).hasMessage("Unable to load component " + MigrationStep1.class);
} finally { } finally {
verifyNoInteractions(migrationStatusListener);
assertThat(logTester.logs()).hasSize(2); assertThat(logTester.logs()).hasSize(2);
assertLogLevel(Level.INFO, "Executing DB migrations...");
assertLogLevel(Level.ERROR, "Executed DB migrations: failure | time=");
assertLogLevel(Level.INFO, "Executing 5 DB migrations...");
assertLogLevel(Level.ERROR, "Executed 2/5 DB migrations: failure | time=");
} }
} }


} }


@Test @Test
public void execute_execute_the_instance_of_type_specified_in_step_in_stream_order() {
void execute_execute_the_instance_of_type_specified_in_step_in_stream_order() {
migrationContainer.add(MigrationStep1.class, MigrationStep2.class, MigrationStep3.class); migrationContainer.add(MigrationStep1.class, MigrationStep2.class, MigrationStep3.class);
((SpringComponentContainer) migrationContainer).startComponents(); ((SpringComponentContainer) migrationContainer).startComponents();


underTest.execute(asList( underTest.execute(asList(
registeredStepOf(1, MigrationStep2.class),
registeredStepOf(2, MigrationStep1.class),
registeredStepOf(3, MigrationStep3.class)), new NoOpMigrationStatusListener());
registeredStepOf(1, MigrationStep2.class),
registeredStepOf(2, MigrationStep1.class),
registeredStepOf(3, MigrationStep3.class)),
migrationStatusListener);


assertThat(SingleCallCheckerMigrationStep.calledSteps) assertThat(SingleCallCheckerMigrationStep.calledSteps)
.containsExactly(MigrationStep2.class, MigrationStep1.class, MigrationStep3.class); .containsExactly(MigrationStep2.class, MigrationStep1.class, MigrationStep3.class);
assertThat(logTester.logs()).hasSize(8); assertThat(logTester.logs()).hasSize(8);
assertLogLevel(Level.INFO, assertLogLevel(Level.INFO,
"Executing DB migrations...",
"#1 '1-MigrationStep2'...",
"#1 '1-MigrationStep2': success | time=",
"#2 '2-MigrationStep1'...",
"#2 '2-MigrationStep1': success | time=",
"#3 '3-MigrationStep3'...",
"#3 '3-MigrationStep3': success | time=",
"Executed DB migrations: success | time=");
"Executing 5 DB migrations...",
"3/5 #1 '1-MigrationStep2'...",
"3/5 #1 '1-MigrationStep2': success | time=",
"3/5 #2 '2-MigrationStep1'...",
"3/5 #2 '2-MigrationStep1': success | time=",
"3/5 #3 '3-MigrationStep3'...",
"3/5 #3 '3-MigrationStep3': success | time=",
"Executed 2/5 DB migrations: success | time=");


assertThat(migrationContainer.getComponentByType(MigrationStep1.class).isCalled()).isTrue(); assertThat(migrationContainer.getComponentByType(MigrationStep1.class).isCalled()).isTrue();
assertThat(migrationContainer.getComponentByType(MigrationStep2.class).isCalled()).isTrue(); assertThat(migrationContainer.getComponentByType(MigrationStep2.class).isCalled()).isTrue();
assertThat(migrationContainer.getComponentByType(MigrationStep3.class).isCalled()).isTrue(); assertThat(migrationContainer.getComponentByType(MigrationStep3.class).isCalled()).isTrue();
verify(migrationStatusListener, times(3)).onMigrationStepCompleted();

} }


@Test @Test
public void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_SQLException() {
void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_SQLException() {
migrationContainer.add(MigrationStep2.class, SqlExceptionFailingMigrationStep.class, MigrationStep3.class); migrationContainer.add(MigrationStep2.class, SqlExceptionFailingMigrationStep.class, MigrationStep3.class);
List<RegisteredMigrationStep> steps = asList( List<RegisteredMigrationStep> steps = asList(
registeredStepOf(1, MigrationStep2.class), registeredStepOf(1, MigrationStep2.class),


((SpringComponentContainer) migrationContainer).startComponents(); ((SpringComponentContainer) migrationContainer).startComponents();
try { try {
underTest.execute(steps, noOpMigrationStatusListener);
underTest.execute(steps, migrationStatusListener);
fail("a MigrationStepExecutionException should have been thrown"); fail("a MigrationStepExecutionException should have been thrown");
} catch (MigrationStepExecutionException e) { } catch (MigrationStepExecutionException e) {
assertThat(e).hasMessage("Execution of migration step #2 '2-SqlExceptionFailingMigrationStep' failed"); assertThat(e).hasMessage("Execution of migration step #2 '2-SqlExceptionFailingMigrationStep' failed");
} finally { } finally {
assertThat(logTester.logs()).hasSize(6); assertThat(logTester.logs()).hasSize(6);
assertLogLevel(Level.INFO, assertLogLevel(Level.INFO,
"Executing DB migrations...",
"#1 '1-MigrationStep2'...",
"#1 '1-MigrationStep2': success | time=",
"#2 '2-SqlExceptionFailingMigrationStep'...");
"Executing 5 DB migrations...",
"3/5 #1 '1-MigrationStep2'...",
"3/5 #1 '1-MigrationStep2': success | time=",
"3/5 #2 '2-SqlExceptionFailingMigrationStep'...");
assertLogLevel(Level.ERROR, assertLogLevel(Level.ERROR,
"#2 '2-SqlExceptionFailingMigrationStep': failure | time=",
"Executed DB migrations: failure | time=");
"3/5 #2 '2-SqlExceptionFailingMigrationStep': failure | time=",
"Executed 2/5 DB migrations: failure | time=");
} }
verify(migrationStatusListener, times(1)).onMigrationStepCompleted();
} }


@Test @Test
public void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_any_exception() {
void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_any_exception() {
migrationContainer.add(MigrationStep2.class, RuntimeExceptionFailingMigrationStep.class, MigrationStep3.class); migrationContainer.add(MigrationStep2.class, RuntimeExceptionFailingMigrationStep.class, MigrationStep3.class);


List<RegisteredMigrationStep> steps = asList( List<RegisteredMigrationStep> steps = asList(


((SpringComponentContainer) migrationContainer).startComponents(); ((SpringComponentContainer) migrationContainer).startComponents();
try { try {
underTest.execute(steps, noOpMigrationStatusListener);
underTest.execute(steps, migrationStatusListener);
fail("should throw MigrationStepExecutionException"); fail("should throw MigrationStepExecutionException");
} catch (MigrationStepExecutionException e) { } catch (MigrationStepExecutionException e) {
assertThat(e).hasMessage("Execution of migration step #2 '2-RuntimeExceptionFailingMigrationStep' failed"); assertThat(e).hasMessage("Execution of migration step #2 '2-RuntimeExceptionFailingMigrationStep' failed");
assertThat(e.getCause()).isSameAs(RuntimeExceptionFailingMigrationStep.THROWN_EXCEPTION); assertThat(e.getCause()).isSameAs(RuntimeExceptionFailingMigrationStep.THROWN_EXCEPTION);
verify(migrationStatusListener, times(1)).onMigrationStepCompleted();
} }
} }



+ 7
- 5
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java View File

*/ */
package org.sonar.server.platform.db.migration; package org.sonar.server.platform.db.migration;


import org.sonar.api.Startable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.sonar.api.Startable;
import org.sonar.server.platform.DefaultServerUpgradeStatus; import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.db.migration.engine.MigrationEngine; import org.sonar.server.platform.db.migration.engine.MigrationEngine;
import org.sonar.server.platform.db.migration.step.NoOpMigrationStatusListener;
import org.sonar.server.platform.db.migration.step.MigrationStatusListenerImpl;


public class AutoDbMigration implements Startable { public class AutoDbMigration implements Startable {
private final DefaultServerUpgradeStatus serverUpgradeStatus; private final DefaultServerUpgradeStatus serverUpgradeStatus;
private final MigrationEngine migrationEngine; private final MigrationEngine migrationEngine;
private final MutableDatabaseMigrationState databaseMigrationState;


public AutoDbMigration(DefaultServerUpgradeStatus serverUpgradeStatus, MigrationEngine migrationEngine) {
public AutoDbMigration(DefaultServerUpgradeStatus serverUpgradeStatus, MigrationEngine migrationEngine, MutableDatabaseMigrationState databaseMigrationState) {
this.serverUpgradeStatus = serverUpgradeStatus; this.serverUpgradeStatus = serverUpgradeStatus;
this.migrationEngine = migrationEngine; this.migrationEngine = migrationEngine;
this.databaseMigrationState = databaseMigrationState;
} }


@Override @Override
public void start() { public void start() {
if (serverUpgradeStatus.isFreshInstall()) { if (serverUpgradeStatus.isFreshInstall()) {
LoggerFactory.getLogger(getClass()).info("Automatically perform DB migration on fresh install"); LoggerFactory.getLogger(getClass()).info("Automatically perform DB migration on fresh install");
migrationEngine.execute(new NoOpMigrationStatusListener());
migrationEngine.execute(new MigrationStatusListenerImpl(databaseMigrationState));
} else if (serverUpgradeStatus.isUpgraded() && serverUpgradeStatus.isAutoDbUpgrade()) { } else if (serverUpgradeStatus.isUpgraded() && serverUpgradeStatus.isAutoDbUpgrade()) {
LoggerFactory.getLogger(getClass()).info("Automatically perform DB migration, as automatic database upgrade is enabled"); LoggerFactory.getLogger(getClass()).info("Automatically perform DB migration, as automatic database upgrade is enabled");
migrationEngine.execute(new NoOpMigrationStatusListener());
migrationEngine.execute(new MigrationStatusListenerImpl(databaseMigrationState));
} }
} }



+ 5
- 4
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java View File

@Rule @Rule
public LogTester logTester = new LogTester(); public LogTester logTester = new LogTester();


private DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
private DefaultServerUpgradeStatus serverUpgradeStatus = mock(DefaultServerUpgradeStatus.class);
private MigrationEngine migrationEngine = mock(MigrationEngine.class);
private AutoDbMigration underTest = new AutoDbMigration(serverUpgradeStatus, migrationEngine);
private final DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
private final DefaultServerUpgradeStatus serverUpgradeStatus = mock(DefaultServerUpgradeStatus.class);
private final MigrationEngine migrationEngine = mock(MigrationEngine.class);
private final MutableDatabaseMigrationState mutableDatabaseMigrationState = mock(MutableDatabaseMigrationState.class);
private final AutoDbMigration underTest = new AutoDbMigration(serverUpgradeStatus, migrationEngine, mutableDatabaseMigrationState);


@Test @Test
public void start_runs_MigrationEngine_on_h2_if_fresh_install() { public void start_runs_MigrationEngine_on_h2_if_fresh_install() {

Loading…
Cancel
Save