]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22141 added new endpoint /api/v2/system/migrations-status
authorlukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com>
Mon, 29 Apr 2024 10:22:39 +0000 (12:22 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 8 May 2024 20:02:44 +0000 (20:02 +0000)
12 files changed:
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationStatusAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/MigrateDbAction.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/StatusActionTest.java

index 2ee65bc6623bd9bf04edf890187977ee51bd102e..82bb1204e1cae8fe159b05f0d899b3d0de304c1d 100644 (file)
@@ -24,8 +24,34 @@ import javax.annotation.CheckForNull;
 
 public interface DatabaseMigrationState {
 
+  String UNSUPPORTED_DATABASE_MIGRATION_STATUS = "Unsupported DatabaseMigration status";
+  String NO_CONNECTION_TO_DB = "Cannot connect to Database.";
+
   enum Status {
-    NONE, RUNNING, FAILED, SUCCEEDED
+    NONE("NO_MIGRATION", "Database is up-to-date, no migration needed."),
+    RUNNING("MIGRATION_RUNNING", "Database migration is running."),
+    FAILED("MIGRATION_FAILED", "Migration failed: %s.<br/> Please check logs."),
+    SUCCEEDED("MIGRATION_SUCCEEDED", "Migration succeeded."),
+    STATUS_NOT_SUPPORTED("NOT_SUPPORTED", "Upgrade is not supported on embedded database."),
+    MIGRATION_REQUIRED("MIGRATION_REQUIRED", "Database migration is required. DB migration can be started using WS /api/system/migrate_db.");
+
+    private final String stringRepresentation;
+    private final String message;
+
+    Status(String stringRepresentation, String message) {
+      this.stringRepresentation = stringRepresentation;
+      this.message = message;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    @Override
+    public String toString() {
+      return stringRepresentation;
+    }
+
   }
 
   /**
index b1abd6013b4b63a63bd167e21d85c3ed6a74a3bc..a4e820e1be51d46acbd749d6b4f5f227a1674012 100644 (file)
@@ -23,6 +23,7 @@ public class WebApiEndpoints {
   private static final String SYSTEM_ENDPOINTS = "/system";
   public static final String LIVENESS_ENDPOINT = SYSTEM_ENDPOINTS + "/liveness";
   public static final String HEALTH_ENDPOINT = SYSTEM_ENDPOINTS + "/health";
+  public static final String DATABASE_MIGRATIONS_ENDPOINT = SYSTEM_ENDPOINTS + "/migrations-status";
 
   public static final String USERS_MANAGEMENT_DOMAIN = "/users-management";
   public static final String USER_ENDPOINT = USERS_MANAGEMENT_DOMAIN + "/users";
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
new file mode 100644 (file)
index 0000000..d585473
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.api.system.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import java.util.Optional;
+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;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.NO_CONNECTION_TO_DB;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.UNSUPPORTED_DATABASE_MIGRATION_STATUS;
+import static org.sonar.server.v2.WebApiEndpoints.DATABASE_MIGRATIONS_ENDPOINT;
+
+@RestController
+@RequestMapping(DATABASE_MIGRATIONS_ENDPOINT)
+public class DatabaseMigrationsController {
+
+  private final DatabaseVersion databaseVersion;
+  private final DatabaseMigrationState databaseMigrationState;
+  private final Database database;
+
+  public DatabaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, Database database) {
+    this.databaseVersion = databaseVersion;
+    this.databaseMigrationState = databaseMigrationState;
+    this.database = database;
+  }
+
+  @Operation(summary = "Gets the status of ongoing database migrations, if any", description = "Return the detailed status of ongoing database migrations" +
+    " including starting date. If no migration is ongoing or needed it is still possible to call this endpoint and receive appropriate information.")
+  @GetMapping
+  public DatabaseMigrationsResponse getStatus() {
+    Optional<Long> currentVersion = databaseVersion.getVersion();
+    checkState(currentVersion.isPresent(), NO_CONNECTION_TO_DB);
+    DatabaseVersion.Status status = databaseVersion.getStatus();
+    if (status == DatabaseVersion.Status.UP_TO_DATE || status == DatabaseVersion.Status.REQUIRES_DOWNGRADE) {
+      return new DatabaseMigrationsResponse(databaseMigrationState);
+    } else if (!database.getDialect().supportsMigration()) {
+      return new DatabaseMigrationsResponse(DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED);
+    } else {
+      return switch (databaseMigrationState.getStatus()) {
+        case RUNNING, FAILED, SUCCEEDED -> new DatabaseMigrationsResponse(databaseMigrationState);
+        case NONE -> new DatabaseMigrationsResponse(DatabaseMigrationState.Status.MIGRATION_REQUIRED);
+        default -> throw new IllegalArgumentException(UNSUPPORTED_DATABASE_MIGRATION_STATUS);
+      };
+    }
+
+  }
+
+  public record DatabaseMigrationsResponse(String status, @Nullable String startedAt, @Nullable String message) {
+
+    public DatabaseMigrationsResponse(DatabaseMigrationState state) {
+      this(state.getStatus().toString(),
+        DateString.from(state.getStartedAt()),
+        state.getError() != null ? state.getError().getMessage() : state.getStatus().getMessage());
+    }
+
+    public DatabaseMigrationsResponse(DatabaseMigrationState.Status status) {
+      this(status.toString(), null, status.getMessage());
+    }
+  }
+
+}
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
new file mode 100644 (file)
index 0000000..1f7cbd6
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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 d63cef691382486fa789093e8e1d4fb3ae31fa5e..7ec4a6387b8f12f2ae5fbcd6e5ec49d0ed7bbba9 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.v2.config;
 import javax.annotation.Nullable;
 import org.sonar.api.platform.Server;
 import org.sonar.api.resources.Languages;
+import org.sonar.db.Database;
 import org.sonar.db.DbClient;
 import org.sonar.server.common.gitlab.config.GitlabConfigurationService;
 import org.sonar.server.common.group.service.GroupMembershipService;
@@ -41,6 +42,8 @@ import org.sonar.server.common.user.service.UserService;
 import org.sonar.server.health.HealthChecker;
 import org.sonar.server.platform.NodeInformation;
 import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.platform.db.migration.DatabaseMigrationState;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
 import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.user.SystemPasscode;
 import org.sonar.server.user.UserSession;
@@ -69,6 +72,7 @@ import org.sonar.server.v2.api.projects.controller.DefaultBoundProjectsControlle
 import org.sonar.server.v2.api.rule.controller.DefaultRuleController;
 import org.sonar.server.v2.api.rule.controller.RuleController;
 import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator;
+import org.sonar.server.v2.api.system.controller.DatabaseMigrationsController;
 import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
 import org.sonar.server.v2.api.system.controller.HealthController;
 import org.sonar.server.v2.api.system.controller.LivenessController;
@@ -102,6 +106,12 @@ public class PlatformLevel4WebConfig {
     return new HealthController(healthChecker, systemPasscode, nodeInformation, userSession);
   }
 
+  @Bean
+  public DatabaseMigrationsController databaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState,
+    Database database) {
+    return new DatabaseMigrationsController(databaseVersion, databaseMigrationState, database);
+  }
+
   @Bean
   public UsersSearchRestResponseGenerator usersSearchResponseGenerator(UserSession userSession) {
     return new UsersSearchRestResponseGenerator(userSession);
index 04d43567764ad49e1579807eb19be17940b6b348..f235bd848b3f7e995ff923d1750af10dc5d50e59 100644 (file)
  */
 package org.sonar.server.v2.config;
 
+import org.sonar.db.Database;
 import org.sonar.server.common.health.DbConnectionNodeCheck;
 import org.sonar.server.common.platform.LivenessChecker;
 import org.sonar.server.common.platform.SafeModeLivenessCheckerImpl;
 import org.sonar.server.health.HealthChecker;
+import org.sonar.server.platform.db.migration.DatabaseMigrationState;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
 import org.sonar.server.user.SystemPasscode;
+import org.sonar.server.v2.api.system.controller.DatabaseMigrationsController;
 import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
 import org.sonar.server.v2.api.system.controller.HealthController;
 import org.sonar.server.v2.api.system.controller.LivenessController;
@@ -51,4 +55,10 @@ public class SafeModeWebConfig {
   public HealthController healthController(HealthChecker healthChecker, SystemPasscode systemPasscode) {
     return new HealthController(healthChecker, systemPasscode);
   }
+
+  @Bean
+  public DatabaseMigrationsController databaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState,
+    Database database) {
+    return new DatabaseMigrationsController(databaseVersion, databaseMigrationState, database);
+  }
 }
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
new file mode 100644 (file)
index 0000000..5b2bc2d
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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.api.system.controller;
+
+import java.util.Date;
+import java.util.Optional;
+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.version.DatabaseVersion;
+import org.sonar.server.v2.api.ControllerTester;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.FAILED;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.NONE;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.RUNNING;
+import static org.sonar.server.v2.WebApiEndpoints.DATABASE_MIGRATIONS_ENDPOINT;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+class DatabaseMigrationsControllerTest {
+
+  private static final Date SOME_DATE = new Date();
+  private final DatabaseVersion databaseVersion = mock();
+  private final DatabaseMigrationState 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));
+
+  @BeforeEach
+  public void before() {
+    when(database.getDialect()).thenReturn(dialect);
+    when(databaseVersion.getVersion()).thenReturn(Optional.of(1L));
+  }
+
+  @Test
+  void getStatus_whenDatabaseHasNoVersion_return500() throws Exception {
+    Mockito.reset(databaseVersion);
+    when(databaseVersion.getVersion()).thenReturn(Optional.empty());
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().is5xxServerError(),
+      content().json("{\"message\":\"Cannot connect to Database.\"}"));
+  }
+
+  @Test
+  void getStatus_migrationNotNeeded_returnUpToDateStatus() throws Exception {
+    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE);
+    when(migrationState.getStatus()).thenReturn(NONE);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"NO_MIGRATION\",\"message\":\"Database is up-to-date, no migration needed.\"}"));
+  }
+
+  @Test
+  void getStatus_whenDowngradeRequired_returnNone() throws Exception {
+    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_DOWNGRADE);
+    when(migrationState.getStatus()).thenReturn(NONE);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"NO_MIGRATION\",\"message\":\"Database is up-to-date, no migration needed.\"}"));
+  }
+
+  @Test
+  void getStatus_whenDbRequiresUpgradeButDialectIsNotSupported_returnNotSupported() throws Exception {
+    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.FRESH_INSTALL);
+    when(dialect.supportsMigration()).thenReturn(false);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"NOT_SUPPORTED\",\"message\":\"Upgrade is not supported on embedded database.\"}"));
+  }
+
+  @Test
+  void getStatus_whenDbMigrationsRunning_returnRunning() throws Exception {
+    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE);
+    when(dialect.supportsMigration()).thenReturn(true);
+    when(migrationState.getStatus()).thenReturn(RUNNING);
+    when(migrationState.getStartedAt()).thenReturn(SOME_DATE);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"MIGRATION_RUNNING\",\"message\":\"Database migration is running.\"}"));
+  }
+
+  @Test
+  void getStatus_whenDbMigrationsFailed_returnFailed() throws Exception {
+    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);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"MIGRATION_FAILED\",\"message\":\"Migration failed: %s.<br/> Please check logs.\"}"));
+  }
+
+  @Test
+  void getStatus_whenDbMigrationsSucceeded_returnSucceeded() throws Exception {
+    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);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"MIGRATION_SUCCEEDED\",\"message\":\"Migration succeeded.\"}"));
+  }
+
+
+  @Test
+  void getStatus_whenMigrationRequired_returnMigrationRequired() throws Exception {
+    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE);
+    when(dialect.supportsMigration()).thenReturn(true);
+    when(migrationState.getStatus()).thenReturn(NONE);
+    when(migrationState.getStartedAt()).thenReturn(SOME_DATE);
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"MIGRATION_REQUIRED\",\"message\":\"Database migration is required. DB migration " +
+        "can be started using WS /api/system/migrate_db.\"}"));
+  }
+
+  @Test
+  void getStatus_whenMigrationFailedWithError_IncludeErrorInResponse() throws Exception {
+    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"));
+
+    mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(),
+      content().json("{\"status\":\"MIGRATION_FAILED\",\"message\":\"error message\"}"));
+  }
+}
index 4668fbfa52a2fc842cacf41e7aa2eaffebde9f52..29bbe25079cddd3f9196477379595d7409bb1f31 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.server.platform.ws;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.server.platform.db.migration.DatabaseMigrationState;
 
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.FAILED;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.MIGRATION_REQUIRED;
 import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.RUNNING;
 
 public class DbMigrationJsonWriter {
@@ -29,21 +31,6 @@ public class DbMigrationJsonWriter {
   static final String FIELD_MESSAGE = "message";
   static final String FIELD_STARTED_AT = "startedAt";
 
-  static final String STATUS_NO_MIGRATION = "NO_MIGRATION";
-  static final String STATUS_NOT_SUPPORTED = "NOT_SUPPORTED";
-  static final String STATUS_MIGRATION_RUNNING = "MIGRATION_RUNNING";
-  static final String STATUS_MIGRATION_FAILED = "MIGRATION_FAILED";
-  static final String STATUS_MIGRATION_SUCCEEDED = "MIGRATION_SUCCEEDED";
-  static final String STATUS_MIGRATION_REQUIRED = "MIGRATION_REQUIRED";
-
-  static final String NO_CONNECTION_TO_DB = "Cannot connect to Database.";
-  static final String UNSUPPORTED_DATABASE_MIGRATION_STATUS = "Unsupported DatabaseMigration status";
-  static final String MESSAGE_NO_MIGRATION_ON_EMBEDDED_DATABASE = "Upgrade is not supported on embedded database.";
-  static final String MESSAGE_MIGRATION_REQUIRED = "Database migration is required. DB migration can be started using WS /api/system/migrate_db.";
-  static final String MESSAGE_STATUS_NONE = "Database is up-to-date, no migration needed.";
-  static final String MESSAGE_STATUS_RUNNING = "Database migration is running.";
-  static final String MESSAGE_STATUS_SUCCEEDED = "Migration succeeded.";
-  static final String MESSAGE_STATUS_FAILED = "Migration failed: %s.<br/> Please check logs.";
 
   private DbMigrationJsonWriter() {
     // static methods only
@@ -51,73 +38,43 @@ public class DbMigrationJsonWriter {
 
   static void write(JsonWriter json, DatabaseMigrationState databaseMigrationState) {
     json.beginObject()
-      .prop(FIELD_STATE, statusToJson(databaseMigrationState.getStatus()))
-      .prop(FIELD_MESSAGE, buildMessage(databaseMigrationState))
+      .prop(FIELD_STATE, databaseMigrationState.getStatus().toString())
+      .prop(FIELD_MESSAGE, writeMessageIncludingError(databaseMigrationState))
       .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt())
       .endObject();
   }
 
+  private static String writeMessageIncludingError(DatabaseMigrationState state) {
+    if (state.getStatus() == FAILED) {
+      Throwable error = state.getError();
+      return String.format(state.getStatus().getMessage(), error != null ? error.getMessage() : "No failure error");
+    } else {
+      return state.getStatus().getMessage();
+    }
+  }
+
   static void writeNotSupportedResponse(JsonWriter json) {
     json.beginObject()
-      .prop(FIELD_STATE, STATUS_NOT_SUPPORTED)
-      .prop(FIELD_MESSAGE, MESSAGE_NO_MIGRATION_ON_EMBEDDED_DATABASE)
+      .prop(FIELD_STATE, DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED.toString())
+      .prop(FIELD_MESSAGE, DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED.getMessage())
       .endObject();
   }
 
   static void writeJustStartedResponse(JsonWriter json, DatabaseMigrationState databaseMigrationState) {
     json.beginObject()
-      .prop(FIELD_STATE, statusToJson(RUNNING))
-      .prop(FIELD_MESSAGE, MESSAGE_STATUS_RUNNING)
+      .prop(FIELD_STATE, RUNNING.toString())
+      .prop(FIELD_MESSAGE, RUNNING.getMessage())
       .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt())
       .endObject();
   }
 
   static void writeMigrationRequiredResponse(JsonWriter json) {
     json.beginObject()
-      .prop(FIELD_STATE, STATUS_MIGRATION_REQUIRED)
-      .prop(FIELD_MESSAGE, MESSAGE_MIGRATION_REQUIRED)
+      .prop(FIELD_STATE, MIGRATION_REQUIRED.toString())
+      .prop(FIELD_MESSAGE, MIGRATION_REQUIRED.getMessage())
       .endObject();
   }
 
-  private static String statusToJson(DatabaseMigrationState.Status status) {
-    switch (status) {
-      case NONE:
-        return STATUS_NO_MIGRATION;
-      case RUNNING:
-        return STATUS_MIGRATION_RUNNING;
-      case FAILED:
-        return STATUS_MIGRATION_FAILED;
-      case SUCCEEDED:
-        return STATUS_MIGRATION_SUCCEEDED;
-      default:
-        throw new IllegalArgumentException(
-          "Unsupported DatabaseMigration.Status " + status + " can not be converted to JSON value");
-    }
-  }
-
-  private static String buildMessage(DatabaseMigrationState databaseMigrationState) {
-    switch (databaseMigrationState.getStatus()) {
-      case NONE:
-        return MESSAGE_STATUS_NONE;
-      case RUNNING:
-        return MESSAGE_STATUS_RUNNING;
-      case SUCCEEDED:
-        return MESSAGE_STATUS_SUCCEEDED;
-      case FAILED:
-        return String.format(MESSAGE_STATUS_FAILED, failureMessage(databaseMigrationState));
-      default:
-        return UNSUPPORTED_DATABASE_MIGRATION_STATUS;
-    }
-  }
-
-  private static String failureMessage(DatabaseMigrationState databaseMigrationState) {
-    Throwable failureError = databaseMigrationState.getError();
-    if (failureError == null) {
-      return "No failure error";
-    }
-    return failureError.getMessage();
-  }
-
   static String statusDescription() {
     return "State values are:" +
       "<ul>" +
index 54a5aac148a83e9aab953989b598f097054abec0..64cddf32db8c8109aa2fc221a611eced07075be7 100644 (file)
@@ -30,8 +30,8 @@ import org.sonar.server.platform.db.migration.DatabaseMigrationState;
 import org.sonar.server.platform.db.migration.version.DatabaseVersion;
 
 import static com.google.common.base.Preconditions.checkState;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.NO_CONNECTION_TO_DB;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.UNSUPPORTED_DATABASE_MIGRATION_STATUS;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.NO_CONNECTION_TO_DB;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.UNSUPPORTED_DATABASE_MIGRATION_STATUS;
 import static org.sonar.server.platform.ws.DbMigrationJsonWriter.statusDescription;
 import static org.sonar.server.platform.ws.DbMigrationJsonWriter.write;
 import static org.sonar.server.platform.ws.DbMigrationJsonWriter.writeMigrationRequiredResponse;
index 24b57597be2647bbe0eae5139d1c728775b6de95..155df40441d2e27ebca2d1fba7653242e2ce91d6 100644 (file)
@@ -31,8 +31,8 @@ import org.sonar.server.platform.db.migration.DatabaseMigration;
 import org.sonar.server.platform.db.migration.DatabaseMigrationState;
 
 import static com.google.common.base.Preconditions.checkState;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.NO_CONNECTION_TO_DB;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.UNSUPPORTED_DATABASE_MIGRATION_STATUS;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.NO_CONNECTION_TO_DB;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.UNSUPPORTED_DATABASE_MIGRATION_STATUS;
 import static org.sonar.server.platform.ws.DbMigrationJsonWriter.statusDescription;
 import static org.sonar.server.platform.ws.DbMigrationJsonWriter.write;
 import static org.sonar.server.platform.ws.DbMigrationJsonWriter.writeJustStartedResponse;
index d366e1cee8756f9538a2138cf6c0c550d38e23a6..63b9bd4ccf73f1562cef6dba7a3a9055f8c59088 100644 (file)
@@ -47,20 +47,10 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
 import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.FAILED;
+import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.MIGRATION_REQUIRED;
 import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.NONE;
 import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.RUNNING;
 import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.SUCCEEDED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.MESSAGE_MIGRATION_REQUIRED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.MESSAGE_NO_MIGRATION_ON_EMBEDDED_DATABASE;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.MESSAGE_STATUS_NONE;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.MESSAGE_STATUS_RUNNING;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.MESSAGE_STATUS_SUCCEEDED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.STATUS_MIGRATION_FAILED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.STATUS_MIGRATION_REQUIRED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.STATUS_MIGRATION_RUNNING;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.STATUS_MIGRATION_SUCCEEDED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.STATUS_NOT_SUPPORTED;
-import static org.sonar.server.platform.ws.DbMigrationJsonWriter.STATUS_NO_MIGRATION;
 import static org.sonar.test.JsonAssert.assertJson;
 
 @RunWith(DataProviderRunner.class)
@@ -111,7 +101,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_NO_MIGRATION, MESSAGE_STATUS_NONE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(NONE.toString(), NONE.getMessage()));
   }
 
   // this test will raise a IllegalArgumentException when an unsupported value is added to the Status enum
@@ -133,7 +123,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_NO_MIGRATION, MESSAGE_STATUS_NONE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(NONE.toString(), NONE.getMessage()));
   }
 
   @Test
@@ -144,7 +134,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_NOT_SUPPORTED, MESSAGE_NO_MIGRATION_ON_EMBEDDED_DATABASE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(Status.STATUS_NOT_SUPPORTED.toString(), Status.STATUS_NOT_SUPPORTED.getMessage()));
   }
 
   @Test
@@ -157,7 +147,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_MIGRATION_RUNNING, MESSAGE_STATUS_RUNNING, SOME_DATE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(RUNNING.toString(), RUNNING.getMessage(), SOME_DATE));
   }
 
   @Test
@@ -171,7 +161,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_MIGRATION_FAILED, failedMsg(SOME_THROWABLE_MSG), SOME_DATE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(FAILED.toString(), failedMsg(SOME_THROWABLE_MSG), SOME_DATE));
   }
 
   @Test
@@ -185,7 +175,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_MIGRATION_FAILED, failedMsg(DEFAULT_ERROR_MSG), SOME_DATE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(FAILED.toString(), failedMsg(DEFAULT_ERROR_MSG), SOME_DATE));
   }
 
   @Test
@@ -198,7 +188,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_MIGRATION_SUCCEEDED, MESSAGE_STATUS_SUCCEEDED, SOME_DATE));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(SUCCEEDED.toString(), SUCCEEDED.getMessage(), SOME_DATE));
   }
 
   @Test
@@ -211,7 +201,7 @@ public class DbMigrationStatusActionTest {
 
     TestResponse response = tester.newRequest().execute();
 
-    assertJson(response.getInput()).isSimilarTo(expectedResponse(STATUS_MIGRATION_REQUIRED, MESSAGE_MIGRATION_REQUIRED));
+    assertJson(response.getInput()).isSimilarTo(expectedResponse(MIGRATION_REQUIRED.toString(), MIGRATION_REQUIRED.getMessage()));
   }
 
   @DataProvider
index 36d385a056488a9d1fe2d2f61cec5fc32d5f408c..2424e46c424374919ce46299822eade22ff484d9 100644 (file)
@@ -32,7 +32,6 @@ import org.sonar.server.ws.WsActionTester;
 
 import static com.google.common.base.Predicates.in;
 import static com.google.common.base.Predicates.not;
-import static com.google.common.collect.ImmutableSet.of;
 import static com.google.common.collect.Iterables.filter;
 import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -49,9 +48,9 @@ public class StatusActionTest {
   private static final String STATUS_MIGRATION_NEEDED = "DB_MIGRATION_NEEDED";
   private static final String STATUS_MIGRATION_RUNNING = "DB_MIGRATION_RUNNING";
   private static final String STATUS_RESTARTING = "RESTARTING";
-  private static final Set<DatabaseMigrationState.Status> SUPPORTED_DATABASE_MIGRATION_STATUSES = of(DatabaseMigrationState.Status.FAILED, DatabaseMigrationState.Status.NONE,
-    DatabaseMigrationState.Status.SUCCEEDED, DatabaseMigrationState.Status.RUNNING);
-  private static final Set<Platform.Status> SUPPORTED_PLATFORM_STATUSES = of(Platform.Status.BOOTING, Platform.Status.SAFEMODE, Platform.Status.STARTING, Platform.Status.UP);
+  private static final Set<DatabaseMigrationState.Status> SUPPORTED_DATABASE_MIGRATION_STATUSES = Set.of(DatabaseMigrationState.Status.FAILED, DatabaseMigrationState.Status.NONE,
+    DatabaseMigrationState.Status.SUCCEEDED, DatabaseMigrationState.Status.RUNNING, DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED, DatabaseMigrationState.Status.MIGRATION_REQUIRED);
+  private static final Set<Platform.Status> SUPPORTED_PLATFORM_STATUSES = Set.of(Platform.Status.BOOTING, Platform.Status.SAFEMODE, Platform.Status.STARTING, Platform.Status.UP);
 
   private static Server server = new Dummy51Server();
   private DatabaseMigrationState migrationState = mock(DatabaseMigrationState.class);