]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22141 implement simple linear ETA algorithm for db-migration.
authorSteve Marion <steve.marion@sonarsource.com>
Mon, 6 May 2024 16:03:36 +0000 (18:03 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 8 May 2024 20:02:44 +0000 (20:02 +0000)
13 files changed:
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImpl.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MutableDatabaseMigrationState.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationStateImplTest.java
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java [deleted file]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/CommonWebConfig.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/DbMigrationStatusActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/MigrateDbActionTest.java

index ff2f310e6b524fdab30453b2121080e982418390..fc7bb265c9f077468174d01b69b10af031755330 100644 (file)
@@ -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.
    * <p>
-   * If no migration was ever started, the returned date is {@code null}.
+   * If no migration was ever started, the returned date is empty.
    * </p>
    *
-   * @return a {@link Date} or {@code null}
+   * @return a {@link Date} if present
    */
-  @CheckForNull
-  Date getStartedAt();
+  Optional<Instant> 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<Throwable> 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<Instant> getExpectedFinishDate(Instant now);
 
 }
index 5951e58871b922c4c8f2a15eede847642434a890..0cd28f0c813073c91f7843b73f52efb5cb89d94c 100644 (file)
@@ -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<Instant> 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<Throwable> 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<Instant> 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)));
   }
-
 }
index fb1d64ce71f20922a4b0c009903a09f097ae42c4..2185f168284d8f182d36992f8fb16d5389a6c38b 100644 (file)
  */
 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();
 
index 1c2813b4c3a95c0af397e70cc3578b712036d6e9..3abaab8177252968bcd646e51965d56d204bfd39 100644 (file)
  */
 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));
   }
-  
 }
index 9ddd83466044ba511280ac4d047261cf2cf1b1f5..71ae6b5130e8fc67922f35c676760f25a594cbdd 100644 (file)
@@ -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 {
index 0cab425a9cae68d5ff2902fc783dd5c70ca95032..5e7d7b21233856e832d1b3bcc86229040b41c3df 100644 (file)
@@ -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() {
index 0bc47b5d54e042eb039e0166a6a5c1b14c8e7624..7747e8bdf90acc01bf02ecf42109bf3c48209c7e 100644 (file)
 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 (file)
index 1f7cbd6..0000000
+++ /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();
-  }
-}
index 72204a5d3aaafb8c6cd7d6a62539231a7732aaca..3ba5ee6eb43eb5db5a8e201629eaea669f84d6e2 100644 (file)
@@ -74,5 +74,4 @@ public class CommonWebConfig implements WebMvcConfigurer {
   public BeanFactoryPostProcessor beanFactoryPostProcessor1(SpringDocConfigProperties springDocConfigProperties) {
     return beanFactory -> springDocConfigProperties.setDefaultProducesMediaType(MediaType.APPLICATION_JSON_VALUE);
   }
-
 }
index dec18c62cb9617d3dabcea2879cf65f38fa36b8b..ee5eeb7b2600ed35ab85b7be6dc9512814f82ac7 100644 (file)
  */
 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.<br/> 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\"}"));
index 29bbe25079cddd3f9196477379595d7409bb1f31..753801005454dcddce176180fa4ee752f7ce33ad 100644 (file)
@@ -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();
   }
 
index 63b9bd4ccf73f1562cef6dba7a3a9055f8c59088..c2afd80b44e0e1558bbb04942b88d8619cd99b04 100644 (file)
@@ -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())) + "\"" +
       "}";
   }
 }
index 7e6d2cb7ee875dac4f08b15f6733deb6a0d23893..8513ff7ad888e50c2b10c34c1bc1933e60bfbd3a 100644 (file)
@@ -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)) + "\"" +
       "}";
   }
 }