Sfoglia il codice sorgente

SONAR-22141 add new fields in the response of migrations-status endpoint

pull/3361/head
Matteo Mara 4 settimane fa
parent
commit
c66dbade21
23 ha cambiato i file con 337 aggiunte e 88 eliminazioni
  1. 54
    0
      server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/step/MigrationStatusListenerImplTest.java
  2. 16
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java
  3. 39
    2
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java
  4. 4
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java
  5. 3
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java
  6. 4
    3
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java
  7. 28
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStatusListener.java
  8. 41
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStatusListenerImpl.java
  9. 1
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java
  10. 5
    2
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java
  11. 32
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/NoOpMigrationStatusListener.java
  12. 35
    9
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java
  13. 2
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java
  14. 14
    40
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java
  15. 6
    5
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java
  16. 3
    2
      server/sonar-db-migration/src/testFixtures/java/org/sonar/db/SQDatabase.java
  17. 3
    2
      server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java
  18. 2
    1
      server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java
  19. 3
    2
      server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java
  20. 3
    1
      server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java
  21. 10
    9
      server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java
  22. 18
    5
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java
  23. 11
    2
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java

+ 54
- 0
server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/step/MigrationStatusListenerImplTest.java Vedi File

@@ -0,0 +1,54 @@
/*
* SonarQube
* Copyright (C) 2009-2024 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.db.migration.step;

import org.junit.jupiter.api.Test;
import org.sonar.server.platform.db.migration.DatabaseMigrationStateImpl;

import static org.assertj.core.api.Assertions.assertThat;

class MigrationStatusListenerImplTest {

@Test
void onMigrationStepCompleted_incrementsTheStepsInTheState() {
final DatabaseMigrationStateImpl state = new DatabaseMigrationStateImpl();
final MigrationStatusListenerImpl underTest = new MigrationStatusListenerImpl(state);

assertThat(state.getCompletedMigrations()).isZero();

underTest.onMigrationStepCompleted();

assertThat(state.getCompletedMigrations()).isEqualTo(1);
}

@Test
void onMigrationStart_setsTheCorrectNumberOfTotalSteps() {
final DatabaseMigrationStateImpl state = new DatabaseMigrationStateImpl();
final MigrationStatusListenerImpl underTest = new MigrationStatusListenerImpl(state);
final int totalMigrations = 10;

assertThat(state.getTotalMigrations()).isZero();

underTest.onMigrationsStart(totalMigrations);

assertThat(state.getTotalMigrations()).isEqualTo(totalMigrations);
}

}

+ 16
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java Vedi File

@@ -77,4 +77,20 @@ public interface DatabaseMigrationState {
*/
@CheckForNull
Throwable getError();

/**
* The amount of migrations already completed.
*/
int getCompletedMigrations();

/**
* The total amount of migrations to be performed.
*/
int getTotalMigrations();

/**
* The expected finish timestamp of the migration.
*/
Date getExpectedFinishDate();

}

+ 39
- 2
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java Vedi File

@@ -29,9 +29,13 @@ import javax.annotation.Nullable;
public class DatabaseMigrationStateImpl implements MutableDatabaseMigrationState {
private Status status = Status.NONE;
@Nullable
private Date startedAt;
private Date startedAt = null;
@Nullable
private Throwable error;
private Throwable error = null;
private int completedMigrations = 0;
private int totalMigrations = 0;
@Nullable
private Date completionExpectedAt = null;

@Override
public Status getStatus() {
@@ -60,8 +64,41 @@ public class DatabaseMigrationStateImpl implements MutableDatabaseMigrationState
return error;
}

@Override
public void incrementCompletedMigrations() {
completedMigrations++;
updateExpectedFinishDate();
}

@Override
public int getCompletedMigrations() {
return completedMigrations;
}

@Override
public void setTotalMigrations(int totalMigrations) {
this.totalMigrations = totalMigrations;
}

@Override
public int getTotalMigrations() {
return totalMigrations;
}

@Override
public void setError(@Nullable Throwable error) {
this.error = error;
}

@Override
public Date getExpectedFinishDate() {
return completionExpectedAt;
}

private void updateExpectedFinishDate() {
// Here the logic is to calculate the expected finish date based on the current time and the number of migrations completed and total
// migrations
this.completionExpectedAt = new Date();
}

}

+ 4
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java Vedi File

@@ -28,4 +28,8 @@ public interface MutableDatabaseMigrationState extends DatabaseMigrationState {
void setStartedAt(@Nullable Date startedAt);

void setError(@Nullable Throwable error);

void incrementCompletedMigrations();

void setTotalMigrations(int completedMigrations);
}

+ 3
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java Vedi File

@@ -19,6 +19,8 @@
*/
package org.sonar.server.platform.db.migration.engine;

import org.sonar.server.platform.db.migration.step.MigrationStatusListener;

/**
* This class is responsible for:
* <ul>
@@ -29,5 +31,5 @@ package org.sonar.server.platform.db.migration.engine;
* </ul>
*/
public interface MigrationEngine {
void execute();
void execute(MigrationStatusListener listener);
}

+ 4
- 3
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java Vedi File

@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Optional;
import org.sonar.core.platform.SpringComponentContainer;
import org.sonar.server.platform.db.migration.history.MigrationHistory;
import org.sonar.server.platform.db.migration.step.MigrationStatusListener;
import org.sonar.server.platform.db.migration.step.MigrationSteps;
import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
import org.sonar.server.platform.db.migration.step.MigrationStepsExecutorImpl;
@@ -40,7 +41,7 @@ public class MigrationEngineImpl implements MigrationEngine {
}

@Override
public void execute() {
public void execute(MigrationStatusListener listener) {
MigrationContainer migrationContainer = new MigrationContainerImpl(serverContainer, MigrationStepsExecutorImpl.class);
try {
MigrationStepsExecutor stepsExecutor = migrationContainer.getComponentByType(MigrationStepsExecutor.class);
@@ -50,8 +51,8 @@ public class MigrationEngineImpl implements MigrationEngine {
.map(i -> migrationSteps.readFrom(i + 1))
.orElse(migrationSteps.readAll());

stepsExecutor.execute(steps);
listener.onMigrationsStart(steps.size());
stepsExecutor.execute(steps, listener);

} finally {
migrationContainer.cleanup();

+ 28
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStatusListener.java Vedi File

@@ -0,0 +1,28 @@
/*
* SonarQube
* Copyright (C) 2009-2024 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.db.migration.step;

public interface MigrationStatusListener {

void onMigrationStepCompleted();

void onMigrationsStart(int totalMigrations);

}

+ 41
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStatusListenerImpl.java Vedi File

@@ -0,0 +1,41 @@
/*
* SonarQube
* Copyright (C) 2009-2024 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.db.migration.step;

import org.sonar.server.platform.db.migration.MutableDatabaseMigrationState;

public class MigrationStatusListenerImpl implements MigrationStatusListener {

private final MutableDatabaseMigrationState migrationState;

public MigrationStatusListenerImpl(MutableDatabaseMigrationState migrationState) {
this.migrationState = migrationState;
}

@Override
public void onMigrationStepCompleted() {
migrationState.incrementCompletedMigrations();
}

@Override
public void onMigrationsStart(int totalMigrations) {
migrationState.setTotalMigrations(totalMigrations);
}
}

+ 1
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java Vedi File

@@ -34,5 +34,5 @@ public interface MigrationStepsExecutor {
/**
* @throws MigrationStepExecutionException at the first failing migration step execution
*/
void execute(List<RegisteredMigrationStep> steps);
void execute(List<RegisteredMigrationStep> steps, MigrationStatusListener listener);
}

+ 5
- 2
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java Vedi File

@@ -44,12 +44,15 @@ public class MigrationStepsExecutorImpl implements MigrationStepsExecutor {
}

@Override
public void execute(List<RegisteredMigrationStep> steps) {
public void execute(List<RegisteredMigrationStep> steps, MigrationStatusListener listener) {
Profiler globalProfiler = Profiler.create(LOGGER);
globalProfiler.startInfo(GLOBAL_START_MESSAGE);
boolean allStepsExecuted = false;
try {
steps.forEach(this::execute);
for (RegisteredMigrationStep step : steps) {
this.execute(step);
listener.onMigrationStepCompleted();
}
allStepsExecuted = true;
} finally {
if (allStepsExecuted) {

+ 32
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/NoOpMigrationStatusListener.java Vedi File

@@ -0,0 +1,32 @@
/*
* SonarQube
* Copyright (C) 2009-2024 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.db.migration.step;

public class NoOpMigrationStatusListener implements MigrationStatusListener {
@Override
public void onMigrationStepCompleted() {
// no op
}

@Override
public void onMigrationsStart(int totalMigrations) {
// no op
}
}

+ 35
- 9
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java Vedi File

@@ -20,35 +20,34 @@
package org.sonar.server.platform.db.migration;

import java.util.Date;
import org.junit.Test;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class DatabaseMigrationStateImplTest {
class DatabaseMigrationStateImplTest {
private DatabaseMigrationStateImpl underTest = new DatabaseMigrationStateImpl();

@Test
public void getStatus_returns_NONE_when_component_is_created() {
void getStatus_whenComponentIsCreated_shouldReturnNONE() {
assertThat(underTest.getStatus()).isEqualTo(DatabaseMigrationState.Status.NONE);
}

@Test
public void getStatus_returns_argument_of_setStatus() {
void getStatus_shouldReturnArgumentOfSetStatus() {
for (DatabaseMigrationState.Status status : DatabaseMigrationState.Status.values()) {
underTest.setStatus(status);

assertThat(underTest.getStatus()).isEqualTo(status);
}

}

@Test
public void getStartedAt_returns_null_when_component_is_created() {
void getStartedAt_whenComponentIsCreated_shouldReturnNull() {
assertThat(underTest.getStartedAt()).isNull();
}

@Test
public void getStartedAt_returns_argument_of_setStartedAt() {
void getStartedAt_shouldReturnArgumentOfSetStartedAt() {
Date expected = new Date();
underTest.setStartedAt(expected);

@@ -56,15 +55,42 @@ public class DatabaseMigrationStateImplTest {
}

@Test
public void getError_returns_null_when_component_is_created() {
void getError_whenComponentIsCreated_shouldReturnNull() {
assertThat(underTest.getError()).isNull();
}

@Test
public void getError_returns_argument_of_setError() {
void getError_shouldReturnArgumentOfSetError() {
RuntimeException expected = new RuntimeException();
underTest.setError(expected);

assertThat(underTest.getError()).isSameAs(expected);
}
@Test
void incrementCompletedMigrations_shouldIncrementCompletedMigrations() {
assertThat(underTest.getCompletedMigrations()).isZero();
underTest.incrementCompletedMigrations();
assertThat(underTest.getCompletedMigrations()).isEqualTo(1);
}

@Test
void getTotalMigrations_shouldReturnArgumentOfSetTotalMigrations() {
underTest.setTotalMigrations(10);

assertThat(underTest.getTotalMigrations()).isEqualTo(10);
}

@Test
void incrementCompletedMigrations_shouldUpdateExpectedFinishDate() {
Date startDate = new Date();

underTest.incrementCompletedMigrations();

// At the moment the expected finish date gets update with the timestamp of the last migration completed
assertThat(underTest.getExpectedFinishDate()).isAfterOrEqualTo(startDate);
}
}

+ 2
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java Vedi File

@@ -26,6 +26,7 @@ import org.junit.Test;
import org.sonar.api.Startable;
import org.sonar.core.platform.SpringComponentContainer;
import org.sonar.server.platform.db.migration.step.InternalMigrationStepRegistry;
import org.sonar.server.platform.db.migration.step.MigrationStatusListener;
import org.sonar.server.platform.db.migration.step.MigrationStep;
import org.sonar.server.platform.db.migration.step.MigrationStepRegistryImpl;
import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
@@ -96,7 +97,7 @@ public class MigrationContainerImplTest {

private static class NoOpExecutor implements MigrationStepsExecutor {
@Override
public void execute(List<RegisteredMigrationStep> steps) {
public void execute(List<RegisteredMigrationStep> steps, MigrationStatusListener listener) {

}
}

+ 14
- 40
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java Vedi File

@@ -22,15 +22,13 @@ package org.sonar.server.platform.db.migration.engine;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.sonar.core.platform.SpringComponentContainer;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
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.MigrationSteps;
import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
import org.sonar.server.platform.db.migration.step.NoOpMigrationStatusListener;
import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;

import static java.util.Collections.singletonList;
@@ -40,16 +38,15 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class MigrationEngineImplTest {
class MigrationEngineImplTest {
private final MigrationHistory migrationHistory = mock(MigrationHistory.class);
private final SpringComponentContainer serverContainer = new SpringComponentContainer();
private final MigrationSteps migrationSteps = mock(MigrationSteps.class);
private final StepRegistry stepRegistry = new StepRegistry();
private final MapSettings settings = new MapSettings();
private final MigrationEngineImpl underTest = new MigrationEngineImpl(migrationHistory, serverContainer, migrationSteps);

@Before
public void before() {
@BeforeEach
void before() {
serverContainer.add(migrationSteps);
serverContainer.add(migrationHistory);
serverContainer.add(stepRegistry);
@@ -57,63 +54,40 @@ public class MigrationEngineImplTest {
}

@Test
public void execute_execute_all_steps_of_there_is_no_last_migration_number() {
void execute_execute_all_steps_of_there_is_no_last_migration_number() {
when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.empty());
List<RegisteredMigrationStep> steps = singletonList(new RegisteredMigrationStep(1, "doo", TestMigrationStep.class));
when(migrationSteps.readAll()).thenReturn(steps);

underTest.execute();
underTest.execute(new NoOpMigrationStatusListener());

verify(migrationSteps, times(2)).readAll();
assertThat(stepRegistry.stepRan).isTrue();
}

@Test
public void execute_execute_steps_from_last_migration_number_plus_1() {
void execute_execute_steps_from_last_migration_number_plus_1() {
when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.of(50L));
List<RegisteredMigrationStep> steps = singletonList(new RegisteredMigrationStep(1, "doo", TestMigrationStep.class));
when(migrationSteps.readFrom(51)).thenReturn(steps);
when(migrationSteps.readAll()).thenReturn(steps);

underTest.execute();
underTest.execute(new NoOpMigrationStatusListener());

verify(migrationSteps).readFrom(51);
assertThat(stepRegistry.stepRan).isTrue();
}

private static class NoOpExecutor implements MigrationStepsExecutor {
@Override
public void execute(List<RegisteredMigrationStep> steps) {
// no op
}
}

private static class StepRegistry {
boolean stepRan = false;
}

private static class TestMigrationStep implements MigrationStep {
private final StepRegistry registry;
private record TestMigrationStep(StepRegistry registry) implements MigrationStep {

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

@SupportsBlueGreen
private static class TestBlueGreenMigrationStep implements MigrationStep {
private final StepRegistry registry;

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

+ 6
- 5
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java Vedi File

@@ -47,10 +47,11 @@ public class MigrationStepsExecutorImplTest {
private MigrationContainer migrationContainer = new SimpleMigrationContainer();
private MigrationHistory migrationHistor = mock(MigrationHistory.class);
private MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistor);
private NoOpMigrationStatusListener noOpMigrationStatusListener = new NoOpMigrationStatusListener();

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

assertThat(logTester.logs()).hasSize(2);
assertLogLevel(Level.INFO, "Executing DB migrations...", "Executed DB migrations: success | time=");
@@ -62,7 +63,7 @@ public class MigrationStepsExecutorImplTest {

((SpringComponentContainer) migrationContainer).startComponents();
try {
underTest.execute(steps);
underTest.execute(steps, noOpMigrationStatusListener);
fail("execute should have thrown a IllegalStateException");
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Unable to load component " + MigrationStep1.class);
@@ -94,7 +95,7 @@ public class MigrationStepsExecutorImplTest {
underTest.execute(asList(
registeredStepOf(1, MigrationStep2.class),
registeredStepOf(2, MigrationStep1.class),
registeredStepOf(3, MigrationStep3.class)));
registeredStepOf(3, MigrationStep3.class)), new NoOpMigrationStatusListener());

assertThat(SingleCallCheckerMigrationStep.calledSteps)
.containsExactly(MigrationStep2.class, MigrationStep1.class, MigrationStep3.class);
@@ -124,7 +125,7 @@ public class MigrationStepsExecutorImplTest {

((SpringComponentContainer) migrationContainer).startComponents();
try {
underTest.execute(steps);
underTest.execute(steps, noOpMigrationStatusListener);
fail("a MigrationStepExecutionException should have been thrown");
} catch (MigrationStepExecutionException e) {
assertThat(e).hasMessage("Execution of migration step #2 '2-SqlExceptionFailingMigrationStep' failed");
@@ -153,7 +154,7 @@ public class MigrationStepsExecutorImplTest {

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

+ 3
- 2
server/sonar-db-migration/src/testFixtures/java/org/sonar/db/SQDatabase.java Vedi File

@@ -45,6 +45,7 @@ import org.sonar.server.platform.db.migration.MigrationConfigurationModule;
import org.sonar.server.platform.db.migration.engine.MigrationContainer;
import org.sonar.server.platform.db.migration.engine.MigrationContainerImpl;
import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl;
import org.sonar.server.platform.db.migration.step.MigrationStatusListener;
import org.sonar.server.platform.db.migration.step.MigrationStep;
import org.sonar.server.platform.db.migration.step.MigrationStepExecutionException;
import org.sonar.server.platform.db.migration.step.MigrationSteps;
@@ -144,7 +145,7 @@ public class SQDatabase extends DefaultDatabase {
}

@Override
public void execute(List<RegisteredMigrationStep> steps) {
public void execute(List<RegisteredMigrationStep> steps, MigrationStatusListener listener) {
steps.forEach(step -> execute(step, container));
}

@@ -195,7 +196,7 @@ public class SQDatabase extends DefaultDatabase {
if (stepClass != null) {
steps = filterUntilStep(steps, stepClass);
}
executor.execute(steps);
executor.execute(steps, null);
}

private static List<RegisteredMigrationStep> filterUntilStep(List<RegisteredMigrationStep> steps, Class<? extends MigrationStep> stepClass) {

+ 3
- 2
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java Vedi File

@@ -23,6 +23,7 @@ import org.sonar.api.Startable;
import org.slf4j.LoggerFactory;
import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.db.migration.engine.MigrationEngine;
import org.sonar.server.platform.db.migration.step.NoOpMigrationStatusListener;

public class AutoDbMigration implements Startable {
private final DefaultServerUpgradeStatus serverUpgradeStatus;
@@ -37,10 +38,10 @@ public class AutoDbMigration implements Startable {
public void start() {
if (serverUpgradeStatus.isFreshInstall()) {
LoggerFactory.getLogger(getClass()).info("Automatically perform DB migration on fresh install");
migrationEngine.execute();
migrationEngine.execute(new NoOpMigrationStatusListener());
} else if (serverUpgradeStatus.isUpgraded() && serverUpgradeStatus.isAutoDbUpgrade()) {
LoggerFactory.getLogger(getClass()).info("Automatically perform DB migration, as automatic database upgrade is enabled");
migrationEngine.execute();
migrationEngine.execute(new NoOpMigrationStatusListener());
}
}


+ 2
- 1
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java Vedi File

@@ -27,6 +27,7 @@ import org.sonar.core.util.logs.Profiler;
import org.sonar.server.platform.Platform;
import org.sonar.server.platform.db.migration.DatabaseMigrationState.Status;
import org.sonar.server.platform.db.migration.engine.MigrationEngine;
import org.sonar.server.platform.db.migration.step.MigrationStatusListenerImpl;
import org.sonar.server.platform.db.migration.step.MigrationStepExecutionException;

/**
@@ -102,7 +103,7 @@ public class DatabaseMigrationImpl implements DatabaseMigration {
private void doUpgradeDb() {
Profiler profiler = Profiler.createIfTrace(LOGGER);
profiler.startTrace("Starting DB Migration");
migrationEngine.execute();
migrationEngine.execute(new MigrationStatusListenerImpl(migrationState));
profiler.stopTrace("DB Migration ended");
}


+ 3
- 2
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java Vedi File

@@ -34,6 +34,7 @@ import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.db.migration.engine.MigrationEngine;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -74,7 +75,7 @@ public class AutoDbMigrationTest {

underTest.start();

verify(migrationEngine).execute();
verify(migrationEngine).execute(any());
verifyInfoLog();
}

@@ -96,7 +97,7 @@ public class AutoDbMigrationTest {

underTest.start();

verify(migrationEngine).execute();
verify(migrationEngine).execute(any());
assertThat(logTester.logs(Level.INFO)).contains("Automatically perform DB migration, as automatic database upgrade is enabled");
}


+ 3
- 1
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java Vedi File

@@ -29,6 +29,8 @@ import org.junit.After;
import org.junit.Test;
import org.sonar.server.platform.Platform;
import org.sonar.server.platform.db.migration.engine.MigrationEngine;
import org.sonar.server.platform.db.migration.step.MigrationStatusListener;
import org.sonar.server.platform.db.migration.step.NoOpMigrationStatusListener;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -54,7 +56,7 @@ public class DatabaseMigrationImplConcurrentAccessTest {
private AtomicInteger triggerCount = new AtomicInteger();
private MigrationEngine incrementingMigrationEngine = new MigrationEngine() {
@Override
public void execute() {
public void execute(MigrationStatusListener listener){
// need execute to consume some time to avoid UT to fail because it ran too fast and threads never executed concurrently
try {
Thread.sleep(200);

+ 10
- 9
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java Vedi File

@@ -26,6 +26,7 @@ import org.sonar.server.platform.Platform;
import org.sonar.server.platform.db.migration.engine.MigrationEngine;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
@@ -40,24 +41,24 @@ public class DatabaseMigrationImplTest {
/**
* Implementation of execute runs Runnable synchronously.
*/
private DatabaseMigrationExecutorService executorService = new DatabaseMigrationExecutorServiceAdaptor() {
private final DatabaseMigrationExecutorService executorService = new DatabaseMigrationExecutorServiceAdaptor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
private MutableDatabaseMigrationState migrationState = new DatabaseMigrationStateImpl();
private Platform platform = mock(Platform.class);
private MigrationEngine migrationEngine = mock(MigrationEngine.class);
private InOrder inOrder = inOrder(platform, migrationEngine);
private final MutableDatabaseMigrationState migrationState = new DatabaseMigrationStateImpl();
private final Platform platform = mock(Platform.class);
private final MigrationEngine migrationEngine = mock(MigrationEngine.class);
private final InOrder inOrder = inOrder(platform, migrationEngine);

private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, migrationEngine, platform);
private final DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, migrationEngine, platform);

@Test
public void startit_calls_MigrationEngine_execute() {
underTest.startIt();

inOrder.verify(migrationEngine).execute();
inOrder.verify(migrationEngine).execute(any());
inOrder.verify(platform).doStart();
inOrder.verifyNoMoreInteractions();
}
@@ -103,10 +104,10 @@ public class DatabaseMigrationImplTest {
}

private void mockMigrationThrowsError() {
doThrow(AN_ERROR).when(migrationEngine).execute();
doThrow(AN_ERROR).when(migrationEngine).execute(any());
}

private void mockMigrationDoesNothing() {
doNothing().when(migrationEngine).execute();
doNothing().when(migrationEngine).execute(any());
}
}

+ 18
- 5
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java Vedi File

@@ -20,12 +20,13 @@
package org.sonar.server.v2.api.system.controller;

import io.swagger.v3.oas.annotations.Operation;
import java.text.SimpleDateFormat;
import java.util.Optional;
import java.util.TimeZone;
import javax.annotation.Nullable;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.DatabaseMigrationState;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
import org.sonar.server.v2.common.DateString;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -43,10 +44,13 @@ public class DatabaseMigrationsController {
private final DatabaseMigrationState databaseMigrationState;
private final Database database;

private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

public DatabaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, Database database) {
this.databaseVersion = databaseVersion;
this.databaseMigrationState = databaseMigrationState;
this.database = database;
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}

@Operation(summary = "Gets the status of ongoing database migrations, if any", description = "Return the detailed status of ongoing database migrations" +
@@ -70,16 +74,25 @@ public class DatabaseMigrationsController {

}

public record DatabaseMigrationsResponse(String status, @Nullable String startedAt, @Nullable String message) {
public record DatabaseMigrationsResponse(
String status,
@Nullable Integer completedSteps,
@Nullable Integer totalSteps,
@Nullable String startedAt,
@Nullable String message,
@Nullable String expectedFinishTimestamp) {

public DatabaseMigrationsResponse(DatabaseMigrationState state) {
this(state.getStatus().toString(),
DateString.from(state.getStartedAt()),
state.getError() != null ? state.getError().getMessage() : state.getStatus().getMessage());
state.getCompletedMigrations(),
state.getTotalMigrations(),
state.getStartedAt() != null ? simpleDateFormat.format(state.getStartedAt()) : null,
state.getError() != null ? state.getError().getMessage() : state.getStatus().getMessage(),
state.getExpectedFinishDate() != null ? simpleDateFormat.format(state.getExpectedFinishDate()) : null);
}

public DatabaseMigrationsResponse(DatabaseMigrationState.Status status) {
this(status.toString(), null, status.getMessage());
this(status.toString(), null, null, null, status.getMessage(), null);
}
}


+ 11
- 2
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java Vedi File

@@ -19,8 +19,10 @@
*/
package org.sonar.server.v2.api.system.controller;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import java.util.TimeZone;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -44,6 +46,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
class DatabaseMigrationsControllerTest {

private static final Date SOME_DATE = new Date();
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

private final DatabaseVersion databaseVersion = mock();
private final DatabaseMigrationState migrationState = mock();
private final Dialect dialect = mock(Dialect.class);
@@ -98,9 +102,15 @@ class DatabaseMigrationsControllerTest {
when(dialect.supportsMigration()).thenReturn(true);
when(migrationState.getStatus()).thenReturn(RUNNING);
when(migrationState.getStartedAt()).thenReturn(SOME_DATE);
when(migrationState.getExpectedFinishDate()).thenReturn(SOME_DATE);
when(migrationState.getCompletedMigrations()).thenReturn(1);
when(migrationState.getTotalMigrations()).thenReturn(10);
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));

mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
content().json("{\"status\":\"MIGRATION_RUNNING\",\"message\":\"Database migration is running.\"}"));
content().json("{\"status\":\"MIGRATION_RUNNING\",\"completedSteps\":1,\"totalSteps\":10," +
"\"message\":\"Database migration is running.\",\"expectedFinishTimestamp\":\""+DATE_FORMAT.format(SOME_DATE)+"\"}"));
}

@Test
@@ -125,7 +135,6 @@ class DatabaseMigrationsControllerTest {
content().json("{\"status\":\"MIGRATION_SUCCEEDED\",\"message\":\"Migration succeeded.\"}"));
}


@Test
void getStatus_whenMigrationRequired_returnMigrationRequired() throws Exception {
when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE);

Loading…
Annulla
Salva