From cb1f5878464822e4e0caf660c9689c255f8cdb66 Mon Sep 17 00:00:00 2001 From: Steve Marion Date: Mon, 6 May 2024 18:03:36 +0200 Subject: [PATCH] SONAR-22141 implement simple linear ETA algorithm for db-migration. --- .../db/migration/DatabaseMigrationState.java | 19 +++---- .../migration/DatabaseMigrationStateImpl.java | 38 ++++++------- .../MutableDatabaseMigrationState.java | 7 +-- .../DatabaseMigrationStateImplTest.java | 55 ++++++++++++++----- .../db/migration/DatabaseMigrationImpl.java | 4 +- .../migration/DatabaseMigrationImplTest.java | 18 +++--- .../DatabaseMigrationsController.java | 14 ++--- .../sonar/server/v2/common/DateString.java | 37 ------------- .../server/v2/config/CommonWebConfig.java | 1 - .../DatabaseMigrationsControllerTest.java | 33 ++++++----- .../platform/ws/DbMigrationJsonWriter.java | 7 ++- .../ws/DbMigrationStatusActionTest.java | 28 +++++----- .../platform/ws/MigrateDbActionTest.java | 28 +++++----- 13 files changed, 137 insertions(+), 152 deletions(-) delete mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java index ff2f310e6b5..fc7bb265c9f 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java @@ -19,8 +19,9 @@ */ package org.sonar.server.platform.db.migration; +import java.time.Instant; import java.util.Date; -import javax.annotation.CheckForNull; +import java.util.Optional; public interface DatabaseMigrationState { @@ -62,21 +63,19 @@ public interface DatabaseMigrationState { /** * The time and day the last migration was started. *

- * If no migration was ever started, the returned date is {@code null}. + * If no migration was ever started, the returned date is empty. *

* - * @return a {@link Date} or {@code null} + * @return a {@link Date} if present */ - @CheckForNull - Date getStartedAt(); + Optional getStartedAt(); /** * The error of the last migration if it failed. * - * @return a {@link Throwable} or {@code null} + * @return a {@link Throwable} if present. */ - @CheckForNull - Throwable getError(); + Optional getError(); /** * The amount of migrations already completed. @@ -89,8 +88,8 @@ public interface DatabaseMigrationState { int getTotalMigrations(); /** - * The expected finish timestamp of the migration. + * The expected finish timestamp of the migration if known. */ - Date getExpectedFinishDate(); + Optional getExpectedFinishDate(Instant now); } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java index 5951e58871b..0cd28f0c813 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java @@ -19,8 +19,9 @@ */ package org.sonar.server.platform.db.migration; -import java.util.Date; -import javax.annotation.CheckForNull; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; import javax.annotation.Nullable; /** @@ -29,13 +30,11 @@ import javax.annotation.Nullable; public class DatabaseMigrationStateImpl implements MutableDatabaseMigrationState { private Status status = Status.NONE; @Nullable - private Date startedAt = null; + private Instant startedAt = null; @Nullable private Throwable error = null; private int completedMigrations = 0; private int totalMigrations = 0; - @Nullable - private Date completionExpectedAt = null; @Override public Status getStatus() { @@ -48,26 +47,23 @@ public class DatabaseMigrationStateImpl implements MutableDatabaseMigrationState } @Override - @CheckForNull - public Date getStartedAt() { - return startedAt; + public Optional getStartedAt() { + return Optional.ofNullable(startedAt); } @Override - public void setStartedAt(@Nullable Date startedAt) { + public void setStartedAt(Instant startedAt) { this.startedAt = startedAt; } @Override - @CheckForNull - public Throwable getError() { - return error; + public Optional getError() { + return Optional.ofNullable(error); } @Override public void incrementCompletedMigrations() { completedMigrations++; - updateExpectedFinishDate(); } @Override @@ -91,14 +87,12 @@ public class DatabaseMigrationStateImpl implements MutableDatabaseMigrationState } @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(); + public Optional getExpectedFinishDate(Instant now) { + if (startedAt == null || totalMigrations == 0 || completedMigrations == 0) { + return Optional.empty(); + } + Duration elapsed = Duration.between(startedAt, now); + double progress = (double) completedMigrations / totalMigrations; + return Optional.of(startedAt.plusMillis((long) (elapsed.toMillis() / progress))); } - } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java index fb1d64ce71f..2185f168284 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java @@ -19,15 +19,14 @@ */ package org.sonar.server.platform.db.migration; -import java.util.Date; -import javax.annotation.Nullable; +import java.time.Instant; public interface MutableDatabaseMigrationState extends DatabaseMigrationState { void setStatus(Status status); - void setStartedAt(@Nullable Date startedAt); + void setStartedAt(Instant startedAt); - void setError(@Nullable Throwable error); + void setError(Throwable error); void incrementCompletedMigrations(); diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java index 1c2813b4c3a..3abaab81772 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java @@ -19,13 +19,16 @@ */ package org.sonar.server.platform.db.migration; -import java.util.Date; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; class DatabaseMigrationStateImplTest { - private DatabaseMigrationStateImpl underTest = new DatabaseMigrationStateImpl(); + private final DatabaseMigrationStateImpl underTest = new DatabaseMigrationStateImpl(); @Test void getStatus_whenComponentIsCreated_shouldReturnNONE() { @@ -42,21 +45,21 @@ class DatabaseMigrationStateImplTest { } @Test - void getStartedAt_whenComponentIsCreated_shouldReturnNull() { - assertThat(underTest.getStartedAt()).isNull(); + void getStartedAt_whenComponentIsCreated_shouldNotBePresent() { + assertThat(underTest.getStartedAt()).isEmpty(); } @Test void getStartedAt_shouldReturnArgumentOfSetStartedAt() { - Date expected = new Date(); + Instant expected = Instant.now(); underTest.setStartedAt(expected); - assertThat(underTest.getStartedAt()).isSameAs(expected); + assertThat(underTest.getStartedAt()).get().isSameAs(expected); } @Test - void getError_whenComponentIsCreated_shouldReturnNull() { - assertThat(underTest.getError()).isNull(); + void getError_whenComponentIsCreated_shouldNotBePresent() { + assertThat(underTest.getError()).isEmpty(); } @Test @@ -64,7 +67,7 @@ class DatabaseMigrationStateImplTest { RuntimeException expected = new RuntimeException(); underTest.setError(expected); - assertThat(underTest.getError()).isSameAs(expected); + assertThat(underTest.getError()).get().isSameAs(expected); } @Test @@ -84,13 +87,37 @@ class DatabaseMigrationStateImplTest { } @Test - void incrementCompletedMigrations_shouldUpdateExpectedFinishDate() { - Date startDate = new Date(); + void when_noStartedMigration_expectedFinishDateShouldBeAbsent() { + Instant startDate = Instant.now(); + Instant later = startDate.plus(1, ChronoUnit.MINUTES); + underTest.setTotalMigrations(2); + + assertThat(underTest.getExpectedFinishDate(later)).isEmpty(); + } + + @Test + void when_noStepCompleted_expectedFinishDateShouldBeAbsent() { + Instant startDate = Instant.now(); + Instant later = startDate.plus(1, ChronoUnit.MINUTES); + + underTest.setStartedAt(startDate); + underTest.setTotalMigrations(2); + + assertThat(underTest.getExpectedFinishDate(later)).isEmpty(); + } + + @Test + void when_StepCompleted_expectedFinishDateShouldBePresent() { + Instant startDate = Instant.now(); + Instant later = startDate.plus(1, ChronoUnit.MINUTES); + Instant expectedEnd = startDate.plus(2, ChronoUnit.MINUTES); + + underTest.setStartedAt(startDate); + underTest.setTotalMigrations(2); underTest.incrementCompletedMigrations(); - // At the moment the expected finish date gets update with the timestamp of the last migration completed - assertThat(underTest.getExpectedFinishDate()).isAfterOrEqualTo(startDate); + assertThat(underTest.getExpectedFinishDate(later)).get(InstanceOfAssertFactories.INSTANT) + .isCloseTo(expectedEnd, within(1, ChronoUnit.SECONDS)); } - } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java index 9ddd8346604..71ae6b5130e 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java @@ -19,7 +19,7 @@ */ package org.sonar.server.platform.db.migration; -import java.util.Date; +import java.time.Instant; import java.util.concurrent.Semaphore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,7 +73,7 @@ public class DatabaseMigrationImpl implements DatabaseMigration { private void doDatabaseMigration() { migrationState.setStatus(Status.RUNNING); - migrationState.setStartedAt(new Date()); + migrationState.setStartedAt(Instant.now()); migrationState.setError(null); Profiler profiler = Profiler.create(LOGGER); try { diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java index 0cab425a9ca..5e7d7b21233 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java @@ -19,7 +19,7 @@ */ package org.sonar.server.platform.db.migration; -import java.util.Date; +import java.time.Instant; import org.junit.Test; import org.mockito.InOrder; import org.sonar.server.platform.Platform; @@ -68,7 +68,7 @@ public class DatabaseMigrationImplTest { underTest.startIt(); assertThat(migrationState.getStatus()).isEqualTo(DatabaseMigrationState.Status.SUCCEEDED); - assertThat(migrationState.getError()).isNull(); + assertThat(migrationState.getError()).isEmpty(); assertThat(migrationState.getStartedAt()).isNotNull(); } @@ -79,7 +79,7 @@ public class DatabaseMigrationImplTest { underTest.startIt(); assertThat(migrationState.getStatus()).isEqualTo(DatabaseMigrationState.Status.FAILED); - assertThat(migrationState.getError()).isSameAs(AN_ERROR); + assertThat(migrationState.getError()).get().isSameAs(AN_ERROR); assertThat(migrationState.getStartedAt()).isNotNull(); } @@ -90,17 +90,19 @@ public class DatabaseMigrationImplTest { underTest.startIt(); assertThat(migrationState.getStatus()).isEqualTo(DatabaseMigrationState.Status.FAILED); - assertThat(migrationState.getError()).isSameAs(AN_ERROR); - Date firstStartDate = migrationState.getStartedAt(); - assertThat(firstStartDate).isNotNull(); + assertThat(migrationState.getError()).get().isSameAs(AN_ERROR); + assertThat(migrationState.getStartedAt()).isPresent(); + Instant firstStartDate = migrationState.getStartedAt().get(); mockMigrationDoesNothing(); underTest.startIt(); assertThat(migrationState.getStatus()).isEqualTo(DatabaseMigrationState.Status.SUCCEEDED); - assertThat(migrationState.getError()).isNull(); - assertThat(migrationState.getStartedAt()).isNotSameAs(firstStartDate); + assertThat(migrationState.getError()).isEmpty(); + assertThat(migrationState.getStartedAt()) + .get() + .isNotSameAs(firstStartDate); } private void mockMigrationThrowsError() { diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java index 0bc47b5d54e..7747e8bdf90 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java @@ -20,9 +20,10 @@ package org.sonar.server.v2.api.system.controller; import io.swagger.v3.oas.annotations.Operation; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; 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; @@ -44,13 +45,10 @@ 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" + @@ -86,9 +84,9 @@ public class DatabaseMigrationsController { this(state.getStatus().toString(), 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); + state.getStartedAt().map(d -> d.atZone(ZoneOffset.UTC)).map(DateTimeFormatter.ISO_DATE_TIME::format).orElse(null), + state.getError().map(Throwable::getMessage).orElse(state.getStatus().getMessage()), + state.getExpectedFinishDate(Instant.now()).map(d -> d.atZone(ZoneOffset.UTC)).map(DateTimeFormatter.ISO_DATE_TIME::format).orElse(null)); } public DatabaseMigrationsResponse(DatabaseMigrationState.Status status) { diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java deleted file mode 100644 index 1f7cbd6c483..00000000000 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.v2.common; - -import java.util.Date; -import javax.annotation.Nullable; - -public class DateString { - - private DateString() { - // intentionally empty - } - - public static String from(@Nullable Date date) { - if (date == null) { - return null; - } - return date.toString(); - } -} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java index 72204a5d3aa..3ba5ee6eb43 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java @@ -74,5 +74,4 @@ public class CommonWebConfig implements WebMvcConfigurer { public BeanFactoryPostProcessor beanFactoryPostProcessor1(SpringDocConfigProperties springDocConfigProperties) { return beanFactory -> springDocConfigProperties.setDefaultProducesMediaType(MediaType.APPLICATION_JSON_VALUE); } - } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java index dec18c62cb9..ee5eeb7b260 100644 --- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java @@ -19,20 +19,22 @@ */ package org.sonar.server.v2.api.system.controller; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Optional; -import java.util.TimeZone; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.sonar.db.Database; import org.sonar.db.dialect.Dialect; import org.sonar.server.platform.db.migration.DatabaseMigrationState; +import org.sonar.server.platform.db.migration.DatabaseMigrationStateImpl; import org.sonar.server.platform.db.migration.version.DatabaseVersion; import org.sonar.server.v2.api.ControllerTester; import org.springframework.test.web.servlet.MockMvc; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.FAILED; @@ -45,11 +47,10 @@ 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 static final Instant SOME_DATE = Instant.now(); + private static final String SOME_DATE_STRING = DateTimeFormatter.ISO_DATE_TIME.format(SOME_DATE.atZone(ZoneOffset.UTC)); private final DatabaseVersion databaseVersion = mock(); - private final DatabaseMigrationState migrationState = mock(); + private final DatabaseMigrationStateImpl migrationState = mock(); private final Dialect dialect = mock(Dialect.class); private final Database database = mock(); private final MockMvc mockMvc = ControllerTester.getMockMvc(new DatabaseMigrationsController(databaseVersion, migrationState, database)); @@ -101,16 +102,14 @@ class DatabaseMigrationsControllerTest { when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(RUNNING); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); - when(migrationState.getExpectedFinishDate()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); + when(migrationState.getExpectedFinishDate(any())).thenReturn(Optional.of(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\",\"completedSteps\":1,\"totalSteps\":10," + - "\"message\":\"Database migration is running.\",\"expectedFinishTimestamp\":\""+DATE_FORMAT.format(SOME_DATE)+"\"}")); + "\"message\":\"Database migration is running.\",\"expectedFinishTimestamp\":\"" + SOME_DATE_STRING + "\"}")); } @Test @@ -118,7 +117,7 @@ class DatabaseMigrationsControllerTest { when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(DatabaseMigrationState.Status.FAILED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), content().json("{\"status\":\"MIGRATION_FAILED\",\"message\":\"Migration failed: %s.
Please check logs.\"}")); @@ -129,7 +128,7 @@ class DatabaseMigrationsControllerTest { when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(DatabaseMigrationState.Status.SUCCEEDED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), content().json("{\"status\":\"MIGRATION_SUCCEEDED\",\"message\":\"Migration succeeded.\"}")); @@ -140,7 +139,7 @@ class DatabaseMigrationsControllerTest { when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(NONE); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), content().json("{\"status\":\"MIGRATION_REQUIRED\",\"message\":\"Database migration is required. DB migration " + @@ -152,8 +151,8 @@ class DatabaseMigrationsControllerTest { when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.FRESH_INSTALL); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(FAILED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); - when(migrationState.getError()).thenReturn(new UnsupportedOperationException("error message")); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); + when(migrationState.getError()).thenReturn(Optional.of(new UnsupportedOperationException("error message"))); mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), content().json("{\"status\":\"MIGRATION_FAILED\",\"message\":\"error message\"}")); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java index 29bbe25079c..75380100545 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java @@ -19,6 +19,7 @@ */ package org.sonar.server.platform.ws; +import java.util.Date; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.platform.db.migration.DatabaseMigrationState; @@ -40,13 +41,13 @@ public class DbMigrationJsonWriter { json.beginObject() .prop(FIELD_STATE, databaseMigrationState.getStatus().toString()) .prop(FIELD_MESSAGE, writeMessageIncludingError(databaseMigrationState)) - .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt()) + .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt().map(Date::from).orElse(null)) .endObject(); } private static String writeMessageIncludingError(DatabaseMigrationState state) { if (state.getStatus() == FAILED) { - Throwable error = state.getError(); + Throwable error = state.getError().orElse(null); return String.format(state.getStatus().getMessage(), error != null ? error.getMessage() : "No failure error"); } else { return state.getStatus().getMessage(); @@ -64,7 +65,7 @@ public class DbMigrationJsonWriter { json.beginObject() .prop(FIELD_STATE, RUNNING.toString()) .prop(FIELD_MESSAGE, RUNNING.getMessage()) - .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt()) + .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt().map(Date::from).orElse(null)) .endObject(); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/DbMigrationStatusActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/DbMigrationStatusActionTest.java index 63b9bd4ccf7..c2afd80b44e 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/DbMigrationStatusActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/DbMigrationStatusActionTest.java @@ -23,14 +23,15 @@ import com.google.common.collect.ImmutableList; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Arrays; -import java.util.Date; import java.util.Optional; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.sonar.api.utils.DateUtils; import org.sonar.db.Database; import org.sonar.db.dialect.Dialect; import org.sonar.server.platform.db.migration.DatabaseMigrationState; @@ -56,9 +57,10 @@ import static org.sonar.test.JsonAssert.assertJson; @RunWith(DataProviderRunner.class) public class DbMigrationStatusActionTest { - private static final Date SOME_DATE = new Date(); + private static final Instant SOME_DATE = Instant.now(); private static final String SOME_THROWABLE_MSG = "blablabla pop !"; private static final String DEFAULT_ERROR_MSG = "No failure error"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); private DatabaseVersion databaseVersion = mock(DatabaseVersion.class); private Database database = mock(Database.class); @@ -77,7 +79,7 @@ public class DbMigrationStatusActionTest { public void verify_example() { when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(RUNNING); - when(migrationState.getStartedAt()).thenReturn(DateUtils.parseDateTime("2015-02-23T18:54:23+0100")); + when(migrationState.getStartedAt()).thenReturn(Optional.of(DATE_TIME_FORMATTER.parse("2015-02-23T18:54:23+0100", Instant::from))); TestResponse response = tester.newRequest().execute(); @@ -143,7 +145,7 @@ public class DbMigrationStatusActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(RUNNING); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); TestResponse response = tester.newRequest().execute(); @@ -156,8 +158,8 @@ public class DbMigrationStatusActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(FAILED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); - when(migrationState.getError()).thenReturn(new UnsupportedOperationException(SOME_THROWABLE_MSG)); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); + when(migrationState.getError()).thenReturn(Optional.of(new UnsupportedOperationException(SOME_THROWABLE_MSG))); TestResponse response = tester.newRequest().execute(); @@ -170,8 +172,8 @@ public class DbMigrationStatusActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(FAILED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); - when(migrationState.getError()).thenReturn(null); // no failure throwable caught + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); + when(migrationState.getError()).thenReturn(Optional.empty()); // no failure throwable caught TestResponse response = tester.newRequest().execute(); @@ -184,7 +186,7 @@ public class DbMigrationStatusActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(SUCCEEDED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); TestResponse response = tester.newRequest().execute(); @@ -197,7 +199,7 @@ public class DbMigrationStatusActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(NONE); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); TestResponse response = tester.newRequest().execute(); @@ -223,11 +225,11 @@ public class DbMigrationStatusActionTest { "}"; } - private static String expectedResponse(String status, String msg, Date date) { + private static String expectedResponse(String status, String msg, Instant date) { return "{" + "\"state\":\"" + status + "\"," + "\"message\":\"" + msg + "\"," + - "\"startedAt\":\"" + DateUtils.formatDateTime(date) + "\"" + + "\"startedAt\":\"" + DATE_TIME_FORMATTER.format(date.atZone(ZoneOffset.systemDefault())) + "\"" + "}"; } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/MigrateDbActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/MigrateDbActionTest.java index 7e6d2cb7ee8..8513ff7ad88 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/MigrateDbActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/MigrateDbActionTest.java @@ -23,14 +23,15 @@ import com.google.common.collect.ImmutableList; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Arrays; -import java.util.Date; import java.util.Optional; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.sonar.api.utils.DateUtils; import org.sonar.db.Database; import org.sonar.db.dialect.Dialect; import org.sonar.server.platform.db.migration.DatabaseMigration; @@ -57,7 +58,7 @@ import static org.sonar.test.JsonAssert.assertJson; @RunWith(DataProviderRunner.class) public class MigrateDbActionTest { - private static final Date SOME_DATE = new Date(); + private static final Instant SOME_DATE = Instant.now(); private static final String SOME_THROWABLE_MSG = "blablabla pop !"; private static final String DEFAULT_ERROR_MSG = "No failure error"; @@ -71,6 +72,7 @@ public class MigrateDbActionTest { private static final String MESSAGE_STATUS_NONE = "Database is up-to-date, no migration needed."; private static final String MESSAGE_STATUS_RUNNING = "Database migration is running."; private static final String MESSAGE_STATUS_SUCCEEDED = "Migration succeeded."; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); private DatabaseVersion databaseVersion = mock(DatabaseVersion.class); private Database database = mock(Database.class); @@ -100,7 +102,7 @@ public class MigrateDbActionTest { public void verify_example() { when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(RUNNING); - when(migrationState.getStartedAt()).thenReturn(DateUtils.parseDateTime("2015-02-23T18:54:23+0100")); + when(migrationState.getStartedAt()).thenReturn(Optional.of(DATE_TIME_FORMATTER.parse("2015-02-23T18:54:23+0100", Instant::from))); TestResponse response = tester.newRequest().execute(); @@ -154,7 +156,7 @@ public class MigrateDbActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(RUNNING); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); TestResponse response = tester.newRequest().execute(); @@ -167,8 +169,8 @@ public class MigrateDbActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(FAILED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); - when(migrationState.getError()).thenReturn(new UnsupportedOperationException(SOME_THROWABLE_MSG)); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); + when(migrationState.getError()).thenReturn(Optional.of(new UnsupportedOperationException(SOME_THROWABLE_MSG))); TestResponse response = tester.newRequest().execute(); @@ -181,8 +183,8 @@ public class MigrateDbActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(FAILED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); - when(migrationState.getError()).thenReturn(null); // no failure throwable caught + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); + when(migrationState.getError()).thenReturn(Optional.empty()); // no failure throwable caught TestResponse response = tester.newRequest().execute(); @@ -195,7 +197,7 @@ public class MigrateDbActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(SUCCEEDED); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); TestResponse response = tester.newRequest().execute(); @@ -208,7 +210,7 @@ public class MigrateDbActionTest { when(databaseVersion.getStatus()).thenReturn(status); when(dialect.supportsMigration()).thenReturn(true); when(migrationState.getStatus()).thenReturn(NONE); - when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getStartedAt()).thenReturn(Optional.of(SOME_DATE)); TestResponse response = tester.newRequest().execute(); @@ -235,11 +237,11 @@ public class MigrateDbActionTest { "}"; } - private static String expectedResponse(String status, String msg, Date date) { + private static String expectedResponse(String status, String msg, Instant date) { return "{" + "\"state\":\"" + status + "\"," + "\"message\":\"" + msg + "\"," + - "\"startedAt\":\"" + DateUtils.formatDateTime(date) + "\"" + + "\"startedAt\":\"" + DATE_TIME_FORMATTER.format(date.atZone(ZoneOffset.UTC)) + "\"" + "}"; } } -- 2.39.5