]> source.dussan.org Git - sonarqube.git/commitdiff
SONARCLOUD-75 Support DB migrations compatible with blue/green deployment
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 12 Jun 2018 21:20:55 +0000 (23:20 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 14 Jun 2018 18:20:53 +0000 (20:20 +0200)
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/SupportsBlueGreen.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java

diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/SupportsBlueGreen.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/SupportsBlueGreen.java
new file mode 100644 (file)
index 0000000..71b5733
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark an instance of {@link org.sonar.server.platform.db.migration.step.MigrationStep}
+ * as compatible with blue/green deployment
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SupportsBlueGreen {
+}
index 027008ac89b0ba912ccc808d4d07d9ec8a788dbd..e32359119ba35894df28cdf926020136ea02b04e 100644 (file)
  */
 package org.sonar.server.platform.db.migration.engine;
 
+import java.util.List;
 import java.util.Optional;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.AnnotationUtils;
 import org.sonar.core.platform.ComponentContainer;
+import org.sonar.process.ProcessProperties;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
 import org.sonar.server.platform.db.migration.history.MigrationHistory;
 import org.sonar.server.platform.db.migration.step.MigrationSteps;
 import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
+import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+
+import static java.lang.String.format;
 
 public class MigrationEngineImpl implements MigrationEngine {
   private final MigrationHistory migrationHistory;
   private final ComponentContainer serverContainer;
   private final MigrationContainerPopulator populator;
   private final MigrationSteps migrationSteps;
+  private final Configuration configuration;
 
   public MigrationEngineImpl(MigrationHistory migrationHistory, ComponentContainer serverContainer,
-    MigrationContainerPopulator populator, MigrationSteps migrationSteps) {
+    MigrationContainerPopulator populator, MigrationSteps migrationSteps, Configuration configuration) {
     this.migrationHistory = migrationHistory;
     this.serverContainer = serverContainer;
     this.populator = populator;
     this.migrationSteps = migrationSteps;
+    this.configuration = configuration;
   }
 
   @Override
   public void execute() {
     MigrationContainer migrationContainer = new MigrationContainerImpl(serverContainer, populator);
-
+    boolean blueGreen = configuration.getBoolean(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey()).orElse(false);
     try {
       MigrationStepsExecutor stepsExecutor = migrationContainer.getComponentByType(MigrationStepsExecutor.class);
       Optional<Long> lastMigrationNumber = migrationHistory.getLastMigrationNumber();
-      if (lastMigrationNumber.isPresent()) {
-        stepsExecutor.execute(migrationSteps.readFrom(lastMigrationNumber.get() + 1));
-      } else {
-        stepsExecutor.execute(migrationSteps.readAll());
+
+      List<RegisteredMigrationStep> steps = lastMigrationNumber
+        .map(i -> migrationSteps.readFrom(i + 1))
+        .orElse(migrationSteps.readAll());
+
+      if (blueGreen) {
+        ensureSupportBlueGreen(steps);
       }
+
+      stepsExecutor.execute(steps);
+
     } finally {
       migrationContainer.cleanup();
     }
   }
+
+  private static void ensureSupportBlueGreen(List<RegisteredMigrationStep> steps) {
+    for (RegisteredMigrationStep step : steps) {
+      if (AnnotationUtils.getAnnotation(step.getStepClass(), SupportsBlueGreen.class) == null) {
+        throw new IllegalStateException(format("All migrations canceled. #%d does not support blue/green deployment: %s",
+          step.getMigrationNumber(), step.getDescription()));
+      }
+    }
+  }
 }
index 9b82616da45448561e2fa3f02c744fe93666fa99..161e95ed1497fae28f8e7bc891a6d6c58d9cf763 100644 (file)
  */
 package org.sonar.server.platform.db.migration.engine;
 
+import java.sql.SQLException;
 import java.util.List;
 import java.util.Optional;
 import org.junit.Test;
-import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.core.platform.ComponentContainer;
+import org.sonar.process.ProcessProperties;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
 import org.sonar.server.platform.db.migration.history.MigrationHistory;
 import org.sonar.server.platform.db.migration.step.MigrationStep;
 import org.sonar.server.platform.db.migration.step.MigrationSteps;
 import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
 import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
 
+import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 public class MigrationEngineImplTest {
@@ -47,8 +55,8 @@ public class MigrationEngineImplTest {
   };
   private MigrationSteps migrationSteps = mock(MigrationSteps.class);
 
-  private Configuration configuration;
-  private MigrationEngineImpl underTest = new MigrationEngineImpl(migrationHistory, serverContainer, populator, migrationSteps, configuration);
+  private MapSettings settings = new MapSettings();
+  private MigrationEngineImpl underTest = new MigrationEngineImpl(migrationHistory, serverContainer, populator, migrationSteps, new ConfigurationBridge(settings));
 
   @Test
   public void execute_execute_all_steps_of_there_is_no_last_migration_number() {
@@ -74,4 +82,43 @@ public class MigrationEngineImplTest {
     verify(stepsExecutor).execute(steps);
   }
 
+  @Test
+  public void execute_steps_in_blue_green_mode() {
+    settings.setProperty(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey(), true);
+    when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.of(50L));
+    List<RegisteredMigrationStep> steps = singletonList(new RegisteredMigrationStep(1, "doo", TestBlueGreenMigrationStep.class));
+    when(migrationSteps.readFrom(51)).thenReturn(steps);
+
+    underTest.execute();
+
+    verify(migrationSteps).readFrom(51);
+    verify(stepsExecutor).execute(steps);
+  }
+
+  @Test
+  public void fail_blue_green_execution_if_some_migrations_are_not_compatible() {
+    settings.setProperty(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey(), true);
+    when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.of(50L));
+    List<RegisteredMigrationStep> steps = asList(
+      new RegisteredMigrationStep(1, "foo", TestBlueGreenMigrationStep.class),
+      new RegisteredMigrationStep(2, "bar", MigrationStep.class));
+    when(migrationSteps.readFrom(51)).thenReturn(steps);
+
+    try {
+      underTest.execute();
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("All migrations canceled. #2 does not support blue/green deployment: bar");
+      verifyZeroInteractions(stepsExecutor);
+    }
+  }
+
+  @SupportsBlueGreen
+  private static class TestBlueGreenMigrationStep implements MigrationStep {
+
+    @Override
+    public void execute() throws SQLException {
+
+    }
+  }
 }