Upgrade command rolling updates for patch releases / step 3: Infinispan/JGroups

Closes #38884

Signed-off-by: Ruchika <ruchika.jha1@ibm.com>
This commit is contained in:
Ruchika Jha 2026-01-21 14:16:18 +00:00 committed by GitHub
parent ad16c642ec
commit dbd8d47036
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 5 deletions

View file

@ -225,8 +225,6 @@ Known limitations:
* If there have been changes to the Account Console or Admin UI in the patch release, and the user opened the Account Console or Admin UI before or during the upgrade, the user might see an error message and be asked to reload the application while navigating in browser during or after the upgrade.
* If the two patch releases of {project_name} use different versions of the embedded Infinispan, no rolling update of {project_name} be performed.
== 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.

View file

@ -1,9 +1,12 @@
package org.keycloak.infinispan.compatibility;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.compatibility.AbstractCompatibilityMetadataProvider;
import org.keycloak.infinispan.util.InfinispanUtils;
import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi;
@ -24,14 +27,28 @@ public class CachingEmbeddedMetadataProvider extends AbstractCompatibilityMetada
@Override
public Map<String, String> customMeta() {
String rawIspnVersion = Version.getVersion();
String rawJgroupsVersion = org.jgroups.Version.printVersion();
if (Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES_V2)) {
rawIspnVersion = majorMinorOf(rawIspnVersion);
rawJgroupsVersion = majorMinorOf(rawJgroupsVersion);
}
return Map.of(
"version", Version.getVersion(),
"jgroupsVersion", org.jgroups.Version.printVersion()
"version", rawIspnVersion,
"jgroupsVersion", rawJgroupsVersion
);
}
@Override
public Stream<String> configKeys() {
return Stream.of(DefaultCacheEmbeddedConfigProviderFactory.CONFIG, DefaultCacheEmbeddedConfigProviderFactory.STACK);
}
private static String majorMinorOf(String version) {
if (version == null || version.isEmpty()) {
return version;
}
// Pattern to grab only the "Major.Minor" (e.g., 16.0)
Matcher matcher = Pattern.compile("^(\\d+\\.\\d+)").matcher(version);
return matcher.find() ? matcher.group(1) : version;
}
}

View file

@ -186,6 +186,28 @@ public class UpdateCommandDistTest {
result.assertError("[%1$s] Rolling Update is not available. '%1$s.version' is incompatible: null".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
}
@Test
public void testRollingUpdatePatchCompatibility(KeycloakDistribution distribution) throws IOException {
var jsonFile = createTempFile("patch-compatible", ".json");
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath(),
"--features", "rolling-updates:v2"
);
result.assertMessage("Metadata:");
assertEquals(0, result.exitCode());
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
var cacheMeta = info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME);
assertTrue(cacheMeta.get("version").matches("^\\d+\\.\\d+$"), "Infinispan version should be Major.Minor");
assertTrue(cacheMeta.get("jgroupsVersion").matches("^\\d+\\.\\d+$"), "JGroups version should be Major.Minor");
result = distribution.run(
UpdateCompatibility.NAME,
UpdateCompatibilityCheck.NAME,
UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(),
"--features", "rolling-updates:v2"
);
result.assertExitCode(CompatibilityResult.ExitCode.ROLLING.value());
result.assertMessage("[OK] Rolling Update is available.");
}
@Test
public void testChangeCacheEmbeddedToRemote(KeycloakDistribution distribution) throws IOException {
var jsonFile = createTempFile("compatible", ".json");