mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
Add experimental feature rolling-updates:v2 that allows rolling updat… (#39751)
...e for patch releases Closes #38882 Signed-off-by: Michal Hajas <mhajas@redhat.com>
This commit is contained in:
parent
077173d24f
commit
88f660b235
@ -132,6 +132,7 @@ public class Profile {
|
||||
LOGOUT_ALL_SESSIONS_V1("Logout all sessions logs out only regular sessions", Type.DEPRECATED, 1),
|
||||
|
||||
ROLLING_UPDATES_V1("Rolling Updates", Type.DEFAULT, 1),
|
||||
ROLLING_UPDATES_V2("Rolling Updates for patch releases", Type.EXPERIMENTAL, 2),
|
||||
|
||||
/**
|
||||
* @see <a href="https://github.com/keycloak/keycloak/issues/37967">Deprecate for removal the Instagram social broker</a>.
|
||||
@ -412,6 +413,13 @@ public class Profile {
|
||||
return getInstance().features.get(feature);
|
||||
}
|
||||
|
||||
public static boolean isAnyVersionOfFeatureEnabled(Feature feature) {
|
||||
return isFeatureEnabled(feature) ||
|
||||
getInstance().getEnabledFeatures()
|
||||
.stream()
|
||||
.anyMatch(f -> Objects.equals(f.getUnversionedKey(), feature.getUnversionedKey()));
|
||||
}
|
||||
|
||||
public ProfileName getName() {
|
||||
return profileName;
|
||||
}
|
||||
@ -424,6 +432,10 @@ public class Profile {
|
||||
return features.entrySet().stream().filter(e -> !e.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<Feature> getEnabledFeatures() {
|
||||
return features.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all features of type "preview" or "preview_disabled_by_default"
|
||||
*/
|
||||
|
||||
@ -33,3 +33,12 @@ In this release, the *Recovery Codes* two-factor authentication is promoted from
|
||||
= New AIA action parameter `skip_if_exists` for WebAuthn register
|
||||
|
||||
Both WebAuthn Register actions (`webauthn-register` and `webauthn-register-passwordless`) now support a parameter `skip_if_exists` when initiated by the application (AIA). The parameter allows to skip the action if the user already has a credential of that type. For more information, see the link:{adminguide_link}#_webauthn_aia[Registering WebAuthn credentials using AIA] chapter in the {adminguide_name}.
|
||||
|
||||
= Experimental support for rolling updates for patch releases
|
||||
|
||||
In the previous release, the Keycloak Operator was enhanced to support performing rolling updates of the Keycloak image if both images contain the same version.
|
||||
This is useful, for example, when switching to an optimized image, changing a theme or a provider source code.
|
||||
|
||||
In this release, we extended this to perform rolling update when the new image contains a future patch release from the same `major.minor` release stream.
|
||||
Read more in https://www.keycloak.org/server/update-compatibility#rolling-updates-for-patch-releases[Update Compatibility Tool]
|
||||
|
||||
|
||||
@ -44,4 +44,4 @@ https://www.keycloak.org/server/caching#_securing_transport_stacks
|
||||
https://www.keycloak.org/observability/grafana-dashboards
|
||||
https://www.keycloak.org/securing-apps/token-exchange*
|
||||
https://www.keycloak.org/operator/rolling-updates
|
||||
https://www.keycloak.org/server/update-compatibility
|
||||
https://www.keycloak.org/server/update-compatibility#rolling-updates-for-patch-releases
|
||||
@ -13,6 +13,12 @@ The outcome shows whether a rolling update is possible or if a recreate update i
|
||||
In its current version, it shows that a rolling update is possible when the {project_name} version is the same for the old and the new version.
|
||||
Future versions of {project_name} might change that behavior to use additional information from the configuration, the image and the version to determine if a rolling update is possible.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In the next iteration of this feature, it is possible to use rolling update strategy also when updating to the following patch release of {project_name}.
|
||||
Refer to <<rolling-updates-for-patch-releases>> section for more details.
|
||||
====
|
||||
|
||||
This is fully scriptable, so your update procedure can use that information to perform a rolling or recreate strategy depending on the change performed.
|
||||
It is also GitOps friendly, as it allows storing the metadata of the previous configuration in a file. Use this file in a CI/CD pipeline with the new configuration to determine if a rolling update is possible or if a recreate update is needed.
|
||||
|
||||
@ -74,7 +80,7 @@ Omitting any configuration options results in incomplete metadata, and could lea
|
||||
=== Checking the Metadata
|
||||
|
||||
This command checks the metadata generated by the previous command and compares it with the current configuration and {project_name} version.
|
||||
If you are upgrading to a new {project_name} version, this command must be executed with the new version.
|
||||
If you are updating to a new {project_name} version, this command must be executed with the new version.
|
||||
|
||||
.Check the metadata from a previous deployment.
|
||||
<@kc.updatecompatibility parameters="check --file=/path/to/file.json"/>
|
||||
@ -106,6 +112,12 @@ If no rolling update is possible, the command provides details about the incompa
|
||||
----
|
||||
<1> In this example, the Keycloak version `26.2.0` is not compatible with version `26.2.1` and a rolling update is not possible.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In the next iteration of this feature, it is possible to use rolling update strategy also when updating to the following patch release of {project_name}.
|
||||
Refer to <<rolling-updates-for-patch-releases>> section for more details.
|
||||
====
|
||||
|
||||
*Command exit code*
|
||||
|
||||
Use the command's exit code to determine the update type in your automation pipeline:
|
||||
@ -131,6 +143,19 @@ m|4
|
||||
The feature `rolling-updates` is disabled.
|
||||
|===
|
||||
|
||||
|
||||
[[rolling-updates-for-patch-releases]]
|
||||
== Rolling updates for patch releases
|
||||
|
||||
WARNING: This behavior is currently in an experimental mode, and it is not recommended for use in production.
|
||||
|
||||
It is possible to configure the {project_name} compatibility command to allow rolling updates when updating from a version to a same patch version from the same `major.minor` release stream.
|
||||
|
||||
To enable this behavior for compatibility check command enable feature `rolling-updates:v2` as shown in the following example.
|
||||
<@kc.updatecompatibility parameters="check --file=/path/to/file.json --features=rolling-updates:v2"/>
|
||||
|
||||
Note there is no change needed when generating metadata using `metadata` command.
|
||||
|
||||
== Further reading
|
||||
|
||||
The {project_name} Operator uses the functionality described above to determine if a rolling update is possible. See the <@links.operator id="rolling-updates" /> {section} and the `Auto` strategy for more information.
|
||||
|
||||
@ -30,8 +30,7 @@ public class CachingCompatibilityMetadataProvider implements CompatibilityMetada
|
||||
private static Map<String, String> remoteInfinispanMetadata() {
|
||||
return Map.of(
|
||||
"mode", "remote",
|
||||
"persistence", Boolean.toString(MultiSiteUtils.isPersistentSessionsEnabled()),
|
||||
"version", Version.getVersion()
|
||||
"persistence", Boolean.toString(MultiSiteUtils.isPersistentSessionsEnabled())
|
||||
);
|
||||
}
|
||||
|
||||
@ -39,7 +38,8 @@ public class CachingCompatibilityMetadataProvider implements CompatibilityMetada
|
||||
return Map.of(
|
||||
"mode", "embedded",
|
||||
"persistence", Boolean.toString(Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)),
|
||||
"version", Version.getVersion()
|
||||
"version", Version.getVersion(),
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ public abstract class AbstractUpdatesCommand extends AbstractCommand implements
|
||||
@Override
|
||||
public void run() {
|
||||
Environment.updateProfile(true);
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES_V1)) {
|
||||
if (!Profile.isAnyVersionOfFeatureEnabled(Profile.Feature.ROLLING_UPDATES_V1)) {
|
||||
printFeatureDisabled();
|
||||
picocli.exit(FEATURE_DISABLED_EXIT_CODE);
|
||||
return;
|
||||
@ -94,13 +94,13 @@ public abstract class AbstractUpdatesCommand extends AbstractCommand implements
|
||||
}
|
||||
|
||||
private void printPreviewWarning() {
|
||||
if (Profile.Feature.ROLLING_UPDATES_V1.getType() == Profile.Feature.Type.PREVIEW) {
|
||||
printError("Warning! This command is '" + Profile.Feature.ROLLING_UPDATES_V1.getType() + "' and is not recommended for use in production. It may change or be removed at a future release.");
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES_V2) && (Profile.Feature.ROLLING_UPDATES_V2.getType() == Profile.Feature.Type.PREVIEW || Profile.Feature.ROLLING_UPDATES_V2.getType() == Profile.Feature.Type.EXPERIMENTAL)) {
|
||||
printError("Warning! This command is '" + Profile.Feature.ROLLING_UPDATES_V2.getType() + "' 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 feature '" + Profile.Feature.ROLLING_UPDATES_V1.getVersionedKey() + "' is not enabled.");
|
||||
printError("Unable to use this command. None of the versions of the feature '" + Profile.Feature.ROLLING_UPDATES_V1.getUnversionedKey() + "' is enabled.");
|
||||
}
|
||||
|
||||
static Map<String, CompatibilityMetadataProvider> loadAllProviders() {
|
||||
|
||||
@ -47,7 +47,7 @@ public class UpdateCommandDistTest {
|
||||
@Test
|
||||
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, DISABLE_FEATURE})
|
||||
public void testFeatureNotEnabled(CLIResult cliResult) {
|
||||
cliResult.assertError("Unable to use this command. The feature 'rolling-updates:v1' is not enabled.");
|
||||
cliResult.assertError("Unable to use this command. None of the versions of the feature 'rolling-updates' is enabled.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -78,8 +78,10 @@ public class UpdateCommandDistTest {
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
assertEquals(Version.VERSION, info.get(KeycloakCompatibilityMetadataProvider.ID).get("version"));
|
||||
assertEquals(org.infinispan.commons.util.Version.getVersion(), info.get(CachingCompatibilityMetadataProvider.ID).get("version"));
|
||||
assertEquals(org.jgroups.Version.printVersion(), info.get(CachingCompatibilityMetadataProvider.ID).get("jgroupsVersion"));
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.ROLLING.value());
|
||||
result.assertMessage("[OK] Rolling Update is available.");
|
||||
result.assertNoError("Rolling Update is not available.");
|
||||
}
|
||||
@ -94,7 +96,8 @@ public class UpdateCommandDistTest {
|
||||
info.put(CachingCompatibilityMetadataProvider.ID, Map.of(
|
||||
"version", org.infinispan.commons.util.Version.getVersion(),
|
||||
"persistence", "true",
|
||||
"mode", "embedded"
|
||||
"mode", "embedded",
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
));
|
||||
JsonSerialization.mapper.writeValue(jsonFile, info);
|
||||
|
||||
@ -107,12 +110,28 @@ public class UpdateCommandDistTest {
|
||||
info.put(CachingCompatibilityMetadataProvider.ID, Map.of(
|
||||
"version", "0.0.0.Final",
|
||||
"persistence", "true",
|
||||
"mode", "embedded"
|
||||
"mode", "embedded",
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
));
|
||||
JsonSerialization.mapper.writeValue(jsonFile, info);
|
||||
|
||||
// incompatible jgroups version
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.version' is incompatible: 0.0.0.Final -> %s.".formatted(CachingCompatibilityMetadataProvider.ID, CachingCompatibilityMetadataProvider.ID, org.infinispan.commons.util.Version.getVersion())); // incompatible infinispan version
|
||||
|
||||
info.put(KeycloakCompatibilityMetadataProvider.ID, Map.of("version", Version.VERSION));
|
||||
info.put(CachingCompatibilityMetadataProvider.ID, Map.of(
|
||||
"version", org.infinispan.commons.util.Version.getVersion(),
|
||||
"persistence", "true",
|
||||
"mode", "embedded",
|
||||
"jgroupsVersion", "0.0.0.Final"
|
||||
));
|
||||
JsonSerialization.mapper.writeValue(jsonFile, info);
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.version' is incompatible: 0.0.0.Final -> %s.".formatted(CachingCompatibilityMetadataProvider.ID, CachingCompatibilityMetadataProvider.ID, org.infinispan.commons.util.Version.getVersion()));
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.jgroupsVersion' is incompatible: 0.0.0.Final -> %s.".formatted(CachingCompatibilityMetadataProvider.ID, CachingCompatibilityMetadataProvider.ID, org.jgroups.Version.printVersion()));
|
||||
}
|
||||
|
||||
private static File createTempFile(String prefix) throws IOException {
|
||||
@ -122,3 +141,4 @@ public class UpdateCommandDistTest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package org.keycloak.compatibility;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
record AggregatedCompatibilityResult(Set<CompatibilityResult> compatibilityResults) implements CompatibilityResult {
|
||||
|
||||
public AggregatedCompatibilityResult(CompatibilityResult compatibilityResult) {
|
||||
this(new HashSet<>());
|
||||
this.compatibilityResults.add(compatibilityResult);
|
||||
}
|
||||
|
||||
public AggregatedCompatibilityResult add(CompatibilityResult a) {
|
||||
compatibilityResults.add(a);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int exitCode() {
|
||||
return compatibilityResults.stream()
|
||||
.anyMatch(r -> r.exitCode() == ExitCode.RECREATE.value())
|
||||
? ExitCode.RECREATE.value() : ExitCode.ROLLING.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> errorMessage() {
|
||||
StringBuilder sb = new StringBuilder("Aggregated incompatible results:\n");
|
||||
for (CompatibilityResult result : compatibilityResults) {
|
||||
sb.append(result.errorMessage()).append("\n");
|
||||
}
|
||||
return Optional.of(sb.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Set<String>> incompatibleAttributes() {
|
||||
return Optional.of(compatibilityResults.stream()
|
||||
.filter(r -> ProviderIncompatibleResult.class.isAssignableFrom(r.getClass()))
|
||||
.map(ProviderIncompatibleResult.class::cast)
|
||||
.flatMap(r -> r.incompatibleAttributes().orElse(Set.of()).stream())
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ package org.keycloak.compatibility;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The result of {@link CompatibilityMetadataProvider#isCompatible(Map)}.
|
||||
@ -48,6 +49,8 @@ public interface CompatibilityResult {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Optional<Set<String>> incompatibleAttributes() {return Optional.empty();}
|
||||
|
||||
static CompatibilityResult providerCompatible(String providerId) {
|
||||
return new ProviderCompatibleResult(Objects.requireNonNull(providerId));
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.keycloak.compatibility;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Internal class to signal that the provider is not compatible with the previous metadata.
|
||||
@ -18,4 +19,9 @@ record ProviderIncompatibleResult(String providerId, String attribute, String pr
|
||||
public Optional<String> errorMessage() {
|
||||
return Optional.of("[%s] Rolling Update is not available. '%s.%s' is incompatible: %s -> %s.".formatted(providerId, providerId, attribute, previousValue, currentValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Set<String>> incompatibleAttributes() {
|
||||
return Optional.of(Set.of(attribute));
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,13 @@ public final class Util {
|
||||
.sorted()
|
||||
.map(key -> compare(provider, key, old.get(key), current.get(key)))
|
||||
.filter(Util::isNotCompatible)
|
||||
.findFirst()
|
||||
.reduce((a, b) -> {
|
||||
if (! (a instanceof AggregatedCompatibilityResult)) {
|
||||
a = new AggregatedCompatibilityResult(a);
|
||||
}
|
||||
|
||||
return ((AggregatedCompatibilityResult) a).add(b);
|
||||
})
|
||||
.orElse(CompatibilityResult.providerCompatible(provider));
|
||||
}
|
||||
|
||||
|
||||
@ -111,6 +111,10 @@ public class ModelVersion {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasSameMajorMinor(ModelVersion v2) {
|
||||
return major == v2.major && minor == v2.minor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ModelVersion)) {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.keycloak.compatibility;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.migration.ModelVersion;
|
||||
|
||||
/**
|
||||
* A {@link CompatibilityMetadataProvider} implementation to provide the Keycloak version.
|
||||
@ -9,14 +12,65 @@ import org.keycloak.common.Version;
|
||||
public class KeycloakCompatibilityMetadataProvider implements CompatibilityMetadataProvider {
|
||||
|
||||
public static final String ID = "keycloak";
|
||||
public static final String VERSION_KEY = "version";
|
||||
private final String version;
|
||||
|
||||
public KeycloakCompatibilityMetadataProvider() {
|
||||
this(Version.VERSION);
|
||||
}
|
||||
|
||||
public KeycloakCompatibilityMetadataProvider(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> metadata() {
|
||||
return Map.of("version", Version.VERSION);
|
||||
return Map.of(VERSION_KEY, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompatibilityResult isCompatible(Map<String, String> other) {
|
||||
CompatibilityResult equalComparison = CompatibilityMetadataProvider.super.isCompatible(other);
|
||||
|
||||
// If V2 feature is enabled, we consider versions upgradable in a rolling way if the other is a previous micro release
|
||||
if (!Util.isNotCompatible(equalComparison) || !Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES_V2)) {
|
||||
return equalComparison;
|
||||
}
|
||||
|
||||
|
||||
// We need to make sure the previous version is not null
|
||||
String otherVersion = other.get(VERSION_KEY);
|
||||
if (otherVersion == null)
|
||||
return equalComparison;
|
||||
|
||||
// Check if only version attribute is incompatible we don't want to allow rolling update if some other metadata didn't match
|
||||
boolean versionMismatch = equalComparison.incompatibleAttributes()
|
||||
.map(erroredAttributes -> erroredAttributes.size() == 1 && erroredAttributes.iterator().next().equals(VERSION_KEY))
|
||||
.orElse(false);
|
||||
|
||||
if (!versionMismatch) {
|
||||
return equalComparison;
|
||||
}
|
||||
|
||||
ModelVersion otherModelVersion = new ModelVersion(otherVersion);
|
||||
ModelVersion currentModelVersion = new ModelVersion(version);
|
||||
|
||||
// Check we are in the same major.minor release stream
|
||||
if (!currentModelVersion.hasSameMajorMinor(otherModelVersion)) {
|
||||
return equalComparison;
|
||||
}
|
||||
|
||||
int otherMicro = otherModelVersion.getMicro();
|
||||
int currentMicro = currentModelVersion.getMicro();
|
||||
|
||||
// Make sure we are updating to a newer or the same micro release and do not allow rolling rollback
|
||||
return currentMicro < otherMicro ?
|
||||
equalComparison :
|
||||
CompatibilityResult.providerCompatible(ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "keycloak";
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,104 @@
|
||||
package org.keycloak.compatibility;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.profile.ProfileConfigResolver;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.compatibility.KeycloakCompatibilityMetadataProvider.VERSION_KEY;
|
||||
|
||||
public class KeycloakCompatibilityMetadataProviderTest {
|
||||
|
||||
@Test
|
||||
public void testMicroVersionUpgradeWorksWithRollingUpdateV2() {
|
||||
// Enable V2 feature
|
||||
Profile.configure(new ProfileConfigResolver() {
|
||||
@Override
|
||||
public Profile.ProfileName getProfileName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureConfig getFeatureConfig(String feature) {
|
||||
return Profile.Feature.ROLLING_UPDATES_V2.getVersionedKey().equals(feature) ? FeatureConfig.ENABLED : FeatureConfig.UNCONFIGURED;
|
||||
}
|
||||
});
|
||||
|
||||
// Make compatibility provider return hardcoded version as we are not able to test this in integration tests with micro versions equal to 0
|
||||
KeycloakCompatibilityMetadataProvider compatibilityProvider = new KeycloakCompatibilityMetadataProvider("999.999.999-Final");
|
||||
|
||||
// Test compatible
|
||||
assertCompatibility(CompatibilityResult.ExitCode.ROLLING, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.999-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.ROLLING, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.998-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.ROLLING, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.999-Final1")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.ROLLING, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.1-Final")));
|
||||
|
||||
// Test incompatible
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.1000-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.998.999-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "998.999.999-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.998.998-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "998.999.998-Final")));
|
||||
|
||||
Profile.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollingUpgradesV1() {
|
||||
Profile.configure();
|
||||
|
||||
// Make compatibility provider return hardcoded version so we can subtract and add to any of major.minor.micro number
|
||||
KeycloakCompatibilityMetadataProvider compatibilityProvider = new KeycloakCompatibilityMetadataProvider("999.999.999-Final") ;
|
||||
|
||||
// Test compatible
|
||||
assertCompatibility(CompatibilityResult.ExitCode.ROLLING, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.999-Final")));
|
||||
|
||||
// Test incompatible
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.998-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.999-Final1")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.997-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.1000-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.998.999-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "998.999.999-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.998.998-Final")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "998.999.998-Final")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollingUpgradeRefusedWithOtherMetadataNotEquals() {
|
||||
// Enable V2 feature
|
||||
Profile.configure(new ProfileConfigResolver() {
|
||||
@Override
|
||||
public Profile.ProfileName getProfileName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureConfig getFeatureConfig(String feature) {
|
||||
return Profile.Feature.ROLLING_UPDATES_V2.getVersionedKey().equals(feature) ? FeatureConfig.ENABLED : FeatureConfig.UNCONFIGURED;
|
||||
}
|
||||
});
|
||||
|
||||
// Make compatibility provider return hardcoded version as we are not able to test this in integration tests with micro versions equal to 0
|
||||
KeycloakCompatibilityMetadataProvider compatibilityProvider = new KeycloakCompatibilityMetadataProvider("999.999.999-Final") {
|
||||
@Override
|
||||
public Map<String, String> metadata() {
|
||||
return Map.of(VERSION_KEY, "999.999.999-Final",
|
||||
"key2", "value2");
|
||||
}
|
||||
};
|
||||
|
||||
// Test compatible
|
||||
assertCompatibility(CompatibilityResult.ExitCode.ROLLING, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.998-Final", "key2", "value2")));
|
||||
|
||||
// Test incompatible
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.998-Final", "key2", "different-value")));
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, compatibilityProvider.isCompatible(Map.of(VERSION_KEY, "999.999.998-Final")));
|
||||
}
|
||||
|
||||
private void assertCompatibility(CompatibilityResult.ExitCode expected, CompatibilityResult actual) {
|
||||
assertEquals("Expected compatibility result was " + expected, expected.exitCode, actual.exitCode());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user