Feature flag: rolling-updates

Closes #36840

Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
This commit is contained in:
Pedro Ruivo 2025-02-06 16:03:50 +00:00 committed by GitHub
parent 690b0e4bef
commit 0f91e67b90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 71 additions and 19 deletions

View File

@ -131,7 +131,9 @@ public class Profile {
USER_EVENT_METRICS("Collect metrics based on user events", Type.PREVIEW),
IPA_TUURA_FEDERATION("IPA-Tuura user federation provider", Type.EXPERIMENTAL)
IPA_TUURA_FEDERATION("IPA-Tuura user federation provider", Type.EXPERIMENTAL),
ROLLING_UPDATES("Rolling Updates", Type.PREVIEW),
;
private final Type type;

View File

@ -443,6 +443,12 @@ Check the https://kubernetes.io/docs/concepts/services-networking/network-polici
The Keycloak Operator offers updates strategies to control how the Operator handles changes to the Keycloak CR.
[CAUTION]
====
While on preview stage, the feature `rolling-updates` must be enabled.
Otherwise, the {project_name} Operator will fail.
====
**Supported Updates Types:**
Rolling Updates:: Update the StatefulSet in a rolling fashion, minimizing downtime (requires multiple replicas).
@ -466,11 +472,14 @@ kind: Keycloak
metadata:
name: example-kc
spec:
features:
enabled:
- rolling-updates # <1>
update:
strategy: Recreate|<not set> # <1>
strategy: Recreate|<not set> # <2>
----
<1> Set the desired update strategy here (Recreate in this example).
<1> Enable preview feature `rolling-updates`.
<2> Set the desired update strategy here (Recreate in this example).
[%autowidth]
.Possible field values

View File

@ -9,7 +9,11 @@ preview="true"
previewDiscussionLink="https://github.com/keycloak/keycloak/discussions/36785"
>
// TODO Link to discussion?
[CAUTION]
====
While on preview stage, the feature `rolling-updates` must be enabled.
Otherwise, the commands will fail.
====
The goal of this tool is to assist with modifying a {project_name} deployment, whether upgrading to a new version, enabling/disabling features, or changing configuration.
The outcome will indicate whether a rolling upgrade is possible or if a recreate upgrade is required.
@ -124,6 +128,10 @@ m|2
m|3
|Rolling Upgrade is not possible.
The deployment must be shut down before applying the new configuration.
m|4
|Rolling Upgrade is not possible.
The feature `rolling-updates` is disabled.
|===
</@tmpl.guide>

View File

@ -50,6 +50,6 @@ bin/kc.[sh|bat] bootstrap-admin ${parameters}
<#macro updatecompatibility parameters>
[source,bash]
----
bin/kc.[sh|bat] update-compatibility ${parameters}
bin/kc.[sh|bat] update-compatibility ${parameters} --features=rolling-updates
----
</#macro>

View File

@ -2,4 +2,4 @@ ARG IMAGE=keycloak
ARG VERSION=latest
FROM $IMAGE:$VERSION
RUN /opt/keycloak/bin/kc.sh build --db=postgres --health-enabled=true
RUN /opt/keycloak/bin/kc.sh build --db=postgres --health-enabled=true --features=rolling-updates

View File

@ -17,6 +17,7 @@
package org.keycloak.operator.testsuite.integration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -27,10 +28,11 @@ import io.quarkus.test.junit.QuarkusTest;
import org.awaitility.Awaitility;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.keycloak.common.Profile;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UpdateSpec;
import org.keycloak.operator.upgrade.UpdateStrategy;
@ -105,11 +107,12 @@ public class UpgradeTest extends BaseOperatorTest {
}
var updateSpec = new UpdateSpec();
updateSpec.setStrategy(updateStrategy);
if (kc.getSpec().getUnsupported() == null) {
kc.getSpec().setUnsupported(new UnsupportedSpec());
}
kc.getSpec().setUpdateSpec(updateSpec);
if (kc.getSpec().getFeatureSpec() == null) {
kc.getSpec().setFeatureSpec(new FeatureSpec());
}
kc.getSpec().getFeatureSpec().setEnabledFeatures(List.of(Profile.Feature.ROLLING_UPDATES.getKey()));
return kc;
}

View File

@ -80,4 +80,8 @@ public abstract class AbstractUpdatesCommand extends AbstractCommand implements
printError("Warning! This command is preview and is not recommended for use in production. It may change or be removed at a future release.");
}
void printFeatureDisabled() {
printError("Unable to use this command. The preview feature 'rolling-updates' is not enabled.");
}
}

View File

@ -20,7 +20,9 @@ package org.keycloak.quarkus.runtime.cli.command;
import java.io.File;
import java.io.IOException;
import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.compatibility.CompatibilityResult;
import org.keycloak.quarkus.runtime.compatibility.ServerInfo;
import org.keycloak.util.JsonSerialization;
import picocli.CommandLine;
@ -41,6 +43,11 @@ public class UpdateCompatibilityCheck extends AbstractUpdatesCommand {
@Override
public void run() {
if (!Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES)) {
printFeatureDisabled();
picocli.exit(CompatibilityResult.FEATURE_DISABLED);
return;
}
printPreviewWarning();
validateConfig();
var info = readServerInfo();

View File

@ -21,7 +21,9 @@ import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.compatibility.CompatibilityResult;
import org.keycloak.quarkus.runtime.compatibility.ServerInfo;
import org.keycloak.util.JsonSerialization;
import picocli.CommandLine;
@ -41,6 +43,11 @@ public class UpdateCompatibilityMetadata extends AbstractUpdatesCommand {
@Override
public void run() {
if (!Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES)) {
printFeatureDisabled();
picocli.exit(CompatibilityResult.FEATURE_DISABLED);
return;
}
printPreviewWarning();
validateConfig();
var info = compatibilityManager.current();

View File

@ -28,7 +28,11 @@ import java.util.Optional;
public interface CompatibilityResult {
int ROLLING_UPGRADE_EXIT_CODE = 0;
int RECREATE_UPGRADE_EXIT_CODE = 4;
// see picocli.CommandLine.ExitCode
// 1 -> software error
// 2 -> usage error
int RECREATE_UPGRADE_EXIT_CODE = 3;
int FEATURE_DISABLED = 4;
/**
* The compatible {@link CompatibilityResult} implementation

View File

@ -41,6 +41,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@RawDistOnly(reason = "Requires creating JSON file to be available between containers")
public class UpdateCommandDistTest {
private static final String ENABLE_FEATURE = "--features=rolling-updates";
@Test
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME})
public void testFeatureNotEnabled(CLIResult cliResult) {
cliResult.assertError("Unable to use this command. The preview feature 'rolling-updates' is not enabled.");
}
@Test
@Launch({UpdateCompatibility.NAME})
public void testMissingSubCommand(CLIResult cliResult) {
@ -48,13 +56,13 @@ public class UpdateCommandDistTest {
}
@Test
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME})
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, ENABLE_FEATURE})
public void testMissingOptionOnSave(CLIResult cliResult) {
cliResult.assertNoMessage("Missing required argument");
}
@Test
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME})
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, ENABLE_FEATURE})
public void testMissingOptionOnCheck(CLIResult cliResult) {
cliResult.assertError("Missing required argument: " + UpdateCompatibilityCheck.INPUT_OPTION_NAME);
}
@ -62,7 +70,7 @@ public class UpdateCommandDistTest {
@Test
public void testCompatible(KeycloakDistribution distribution) throws IOException {
var jsonFile = createTempFile("compatible");
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath());
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertMessage("Metadata:");
assertEquals(0, result.exitCode());
@ -70,7 +78,7 @@ public class UpdateCommandDistTest {
assertEquals(Version.VERSION, info.getVersions().get(CompatibilityManagerImpl.KEYCLOAK_VERSION_KEY));
assertEquals(org.infinispan.commons.util.Version.getVersion(), info.getVersions().get(CompatibilityManagerImpl.INFINISPAN_VERSION_KEY));
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertMessage("[OK] Rolling Upgrade is available.");
result.assertNoError("Rolling Upgrade is not available.");
}
@ -85,7 +93,7 @@ public class UpdateCommandDistTest {
CompatibilityManagerImpl.INFINISPAN_VERSION_KEY, org.infinispan.commons.util.Version.getVersion()));
JsonSerialization.mapper.writeValue(jsonFile, info);
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertError("[Versions] Rolling Upgrade is not available. 'keycloak' is incompatible: Old=0.0.0.Final, New=%s".formatted(Version.VERSION));
// incompatible infinispan version
@ -94,7 +102,7 @@ public class UpdateCommandDistTest {
CompatibilityManagerImpl.INFINISPAN_VERSION_KEY, "0.0.0.Final"));
JsonSerialization.mapper.writeValue(jsonFile, info);
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertError("[Versions] Rolling Upgrade is not available. 'infinispan' is incompatible: Old=0.0.0.Final, New=%s".formatted(org.infinispan.commons.util.Version.getVersion()));
}