Rename operator strategy options

Closes #37090

Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
This commit is contained in:
Pedro Ruivo 2025-02-12 10:21:22 +00:00 committed by GitHub
parent 66a7151bb5
commit 81c65c8174
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 33 additions and 46 deletions

View File

@ -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|<not set> # <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
|`<not set>` (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.

View File

@ -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<UpdateStrategy> findUpdateStrategy(Keycloak keycloak) {
public static UpdateStrategy getUpdateStrategy(Keycloak keycloak) {
return CRDUtils.keycloakSpecOf(keycloak)
.map(KeycloakSpec::getUpdateSpec)
.map(UpdateSpec::getStrategy);
.map(UpdateSpec::getStrategy)
.orElse(DEFAULT);
}
}

View File

@ -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")

View File

@ -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<Keycloak> 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);
};
}

View File

@ -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<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) {
public ForceRecreateUpgradeLogic(Context<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) {
super(context, keycloak, statefulSetResource);
}

View File

@ -34,11 +34,8 @@ import org.keycloak.operator.upgrade.UpgradeType;
* Implements Keycloak 26.0 logic.
* <p>
* 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<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource dependentResource) {

View File

@ -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<UpdateStrategy> 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.

View File

@ -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

View File

@ -124,7 +124,7 @@ spec:
service:
secret: else
update:
strategy: Recreate
strategy: ForceRecreate
unsupported:
podTemplate:
metadata: