From ccf2766838c80bcd45c28e2bb6594fc6a2d92fac Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Tue, 12 Jun 2018 23:20:55 +0200 Subject: [PATCH] SONARCLOUD-75 Support DB migrations compatible with blue/green deployment --- .../db/migration/SupportsBlueGreen.java | 36 +++++++++++++ .../migration/engine/MigrationEngineImpl.java | 37 ++++++++++--- .../engine/MigrationEngineImplTest.java | 53 +++++++++++++++++-- 3 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/SupportsBlueGreen.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 index 00000000000..71b57333630 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/SupportsBlueGreen.java @@ -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 { +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java index 027008ac89b..e32359119ba 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java @@ -19,40 +19,65 @@ */ 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 lastMigrationNumber = migrationHistory.getLastMigrationNumber(); - if (lastMigrationNumber.isPresent()) { - stepsExecutor.execute(migrationSteps.readFrom(lastMigrationNumber.get() + 1)); - } else { - stepsExecutor.execute(migrationSteps.readAll()); + + List 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 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())); + } + } + } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java index 9b82616da45..161e95ed149 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java @@ -19,20 +19,28 @@ */ 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 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 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 { + + } + } } -- 2.39.5