diff --git a/docs/guides/operator/advanced-configuration.adoc b/docs/guides/operator/advanced-configuration.adoc index 87116817ad4..e82f6925b99 100644 --- a/docs/guides/operator/advanced-configuration.adoc +++ b/docs/guides/operator/advanced-configuration.adoc @@ -450,7 +450,6 @@ Otherwise, the {project_name} Operator will fail. ==== **Supported Updates Types:** - Rolling Updates:: Update the StatefulSet in a rolling fashion, minimizing downtime (requires multiple replicas). Recreate Updates:: Scale down the StatefulSet before applying updates, causing temporary downtime. @@ -458,13 +457,6 @@ Recreate Updates:: Scale down the StatefulSet before applying updates, causing t The update strategy is specified within the `spec` section of the Keycloak CR YAML definition. -[NOTE] -==== -During this preview stage, the update strategy defaults to mimicking Keycloak 26.1 or older behavior: -When the Keycloak CR's image field changes, the Operator scales down the StatefulSet before applying the new image, resulting in downtime. -Any configuration change will be a rolling update. -==== - [source,yaml] ---- apiVersion: k8s.keycloak.org/v2alpha1 @@ -476,7 +468,7 @@ spec: enabled: - rolling-updates # <1> update: - strategy: Recreate| # <2> + strategy: RecreateOnImageChange|ForceRecreate|Auto # <2> ---- <1> Enable preview feature `rolling-updates`. <2> Set the desired update strategy here (Recreate in this example). @@ -486,12 +478,12 @@ spec: |=== |Value |Downtime? |Description -|`` (default) +|`RecreateOnImageChange` (default) |On image name or tag change |Mimics Keycloak 26.1 or older behavior. When the image field changes, the StatefulSet is scaled down before applying the new image. -|`Recreate` +|`ForceRecreate` |On any configuration or image change |The StatefulSet is scaled down before applying the new configuration or image. diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/UpdateSpec.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/UpdateSpec.java index b782225ba4a..bac9268bf6c 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/UpdateSpec.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/UpdateSpec.java @@ -17,10 +17,9 @@ package org.keycloak.operator.crds.v2alpha1.deployment.spec; -import java.util.Optional; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.generator.annotation.Default; import io.sundr.builder.annotations.Buildable; import org.keycloak.operator.crds.v2alpha1.CRDUtils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; @@ -31,7 +30,12 @@ import org.keycloak.operator.upgrade.UpdateStrategy; @Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder") public class UpdateSpec { + // those are the default, keep them in sync. + private static final UpdateStrategy DEFAULT = UpdateStrategy.RECREATE_ON_IMAGE_CHANGE; + private static final String DEFAULT_JSON = "RecreateOnImageChange"; + @JsonPropertyDescription("Sets the upgrade strategy to use.") + @Default(DEFAULT_JSON) private UpdateStrategy strategy; public UpdateStrategy getStrategy() { @@ -42,9 +46,10 @@ public class UpdateSpec { this.strategy = strategy; } - public static Optional findUpdateStrategy(Keycloak keycloak) { + public static UpdateStrategy getUpdateStrategy(Keycloak keycloak) { return CRDUtils.keycloakSpecOf(keycloak) .map(KeycloakSpec::getUpdateSpec) - .map(UpdateSpec::getStrategy); + .map(UpdateSpec::getStrategy) + .orElse(DEFAULT); } } diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/UpdateStrategy.java b/operator/src/main/java/org/keycloak/operator/upgrade/UpdateStrategy.java index d9ad176af53..b4796beaaf9 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/UpdateStrategy.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/UpdateStrategy.java @@ -21,9 +21,14 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; public enum UpdateStrategy { + + @JsonPropertyDescription("Shutdown the Keycloak cluster when the image changes.") + @JsonProperty("RecreateOnImageChange") + RECREATE_ON_IMAGE_CHANGE, + @JsonPropertyDescription("Shutdown the Keycloak cluster before applying the new changes.") - @JsonProperty("Recreate") - RECREATE, + @JsonProperty("ForceRecreate") + FORCE_RECREATE, @JsonPropertyDescription("Automatically detects if the Keycloak CR changes requires a rolling or recreate update.") @JsonProperty("Auto") diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java b/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java index 606befc1daa..98f15415adc 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java @@ -23,7 +23,7 @@ import org.keycloak.operator.controllers.KeycloakDeploymentDependentResource; import org.keycloak.operator.controllers.KeycloakUpdateJobDependentResource; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.spec.UpdateSpec; -import org.keycloak.operator.upgrade.impl.AlwaysRecreateUpgradeLogic; +import org.keycloak.operator.upgrade.impl.ForceRecreateUpgradeLogic; import org.keycloak.operator.upgrade.impl.AutoUpgradeLogic; import org.keycloak.operator.upgrade.impl.RecreateOnImageChangeUpgradeLogic; @@ -33,14 +33,11 @@ import org.keycloak.operator.upgrade.impl.RecreateOnImageChangeUpgradeLogic; @ApplicationScoped public class UpgradeLogicFactory { - @SuppressWarnings("removal") public UpgradeLogic create(Keycloak keycloak, Context context, KeycloakDeploymentDependentResource dependentResource, KeycloakUpdateJobDependentResource updateJobDependentResource) { - var strategy = UpdateSpec.findUpdateStrategy(keycloak); - if (strategy.isEmpty()) { - return new RecreateOnImageChangeUpgradeLogic(context, keycloak, dependentResource); - } - return switch (strategy.get()) { - case RECREATE -> new AlwaysRecreateUpgradeLogic(context, keycloak, dependentResource); + var strategy = UpdateSpec.getUpdateStrategy(keycloak); + return switch (strategy) { + case RECREATE_ON_IMAGE_CHANGE -> new RecreateOnImageChangeUpgradeLogic(context, keycloak, dependentResource); + case FORCE_RECREATE -> new ForceRecreateUpgradeLogic(context, keycloak, dependentResource); case AUTO -> new AutoUpgradeLogic(context, keycloak, dependentResource, updateJobDependentResource); }; } diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/impl/AlwaysRecreateUpgradeLogic.java b/operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java similarity index 87% rename from operator/src/main/java/org/keycloak/operator/upgrade/impl/AlwaysRecreateUpgradeLogic.java rename to operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java index befb514c9ee..5387fe6e690 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/impl/AlwaysRecreateUpgradeLogic.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java @@ -30,9 +30,9 @@ import org.keycloak.operator.upgrade.UpgradeType; * An {@link UpgradeLogic} implementation that forces a {@link UpgradeType#RECREATE} on every configuration or image * change. */ -public class AlwaysRecreateUpgradeLogic extends BaseUpgradeLogic { +public class ForceRecreateUpgradeLogic extends BaseUpgradeLogic { - public AlwaysRecreateUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) { + public ForceRecreateUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) { super(context, keycloak, statefulSetResource); } diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java b/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java index 70310b8d79d..6404ff4bd10 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java @@ -34,11 +34,8 @@ import org.keycloak.operator.upgrade.UpgradeType; * Implements Keycloak 26.0 logic. *

* It uses a {@link UpgradeType#RECREATE} if the image changes; otherwise uses {@link UpgradeType#ROLLING}. - * - * @deprecated To be removed when the new zero-downtime feature is completed. */ @SuppressWarnings("ALL") -@Deprecated(forRemoval = true) public class RecreateOnImageChangeUpgradeLogic extends BaseUpgradeLogic { public RecreateOnImageChangeUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource dependentResource) { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/UpgradeTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/UpgradeTest.java index 2d610a21b2f..4cf762c9dc9 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/UpgradeTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/UpgradeTest.java @@ -23,7 +23,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.stream.Stream; import io.fabric8.kubernetes.api.model.batch.v1.Job; import io.fabric8.kubernetes.api.model.batch.v1.JobStatus; @@ -32,7 +31,7 @@ import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.EnumSource; import org.keycloak.common.Profile; import org.keycloak.operator.controllers.KeycloakUpdateJobDependentResource; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; @@ -55,16 +54,8 @@ import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak; @QuarkusTest public class UpgradeTest extends BaseOperatorTest { - private static Stream upgradeStrategy() { - return Stream.of( - null, - UpdateStrategy.RECREATE, - UpdateStrategy.AUTO - ); - } - @ParameterizedTest(name = "testImageChange-{0}") - @MethodSource("upgradeStrategy") + @EnumSource(UpdateStrategy.class) public void testImageChange(UpdateStrategy updateStrategy) throws InterruptedException { var kc = createInitialDeployment(updateStrategy); deployKeycloak(k8sclient, kc, true); @@ -97,7 +88,7 @@ public class UpgradeTest extends BaseOperatorTest { } @ParameterizedTest(name = "testCacheMaxCount-{0}") - @MethodSource("upgradeStrategy") + @EnumSource(UpdateStrategy.class) public void testCacheMaxCount(UpdateStrategy updateStrategy) throws InterruptedException { var kc = createInitialDeployment(updateStrategy); deployKeycloak(k8sclient, kc, true); @@ -105,7 +96,7 @@ public class UpgradeTest extends BaseOperatorTest { // changing the local cache max-count should never use the recreate upgrade type // except if forced by the Keycloak CR. kc.getSpec().getAdditionalOptions().add(new ValueOrSecret("cache-embedded-authorization-max-count", "10")); - var upgradeCondition = updateStrategy == UpdateStrategy.RECREATE ? + var upgradeCondition = updateStrategy == UpdateStrategy.FORCE_RECREATE ? eventuallyRecreateUpgradeStatus(k8sclient, kc) : eventuallyRollingUpgradeStatus(k8sclient, kc); @@ -119,7 +110,7 @@ public class UpgradeTest extends BaseOperatorTest { } @ParameterizedTest(name = "testOptimizedImage-{0}") - @MethodSource("upgradeStrategy") + @EnumSource(UpdateStrategy.class) @EnabledIfSystemProperty(named = OPERATOR_CUSTOM_IMAGE, matches = ".+") public void testOptimizedImage(UpdateStrategy updateStrategy) throws InterruptedException { // In GHA, the custom image is an optimized image of the base image. diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java index 07c5bb64ea7..ca4f7b21557 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java @@ -262,7 +262,7 @@ public class CRSerializationTest { assertNotNull(updateSpec); var upgradeStrategy = updateSpec.getStrategy(); assertNotNull(upgradeStrategy); - assertEquals(UpdateStrategy.RECREATE, upgradeStrategy); + assertEquals(UpdateStrategy.FORCE_RECREATE, upgradeStrategy); } @Test diff --git a/operator/src/test/resources/test-serialization-keycloak-cr.yml b/operator/src/test/resources/test-serialization-keycloak-cr.yml index 934e454c9a0..69c954e3301 100644 --- a/operator/src/test/resources/test-serialization-keycloak-cr.yml +++ b/operator/src/test/resources/test-serialization-keycloak-cr.yml @@ -124,7 +124,7 @@ spec: service: secret: else update: - strategy: Recreate + strategy: ForceRecreate unsupported: podTemplate: metadata: