mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-22 07:09:03 -06:00
Implement CompatibilityMetadataProvider for Cache CLI args
Closes #41138 Signed-off-by: Ryan Emerson <remerson@redhat.com>
This commit is contained in:
@@ -56,6 +56,12 @@
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-api</artifactId>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.keycloak.compatibility;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.Config;
|
||||
|
||||
|
||||
public abstract class AbstractCompatibilityMetadataProvider implements CompatibilityMetadataProvider {
|
||||
|
||||
final String spi;
|
||||
final Config.Scope config;
|
||||
|
||||
public AbstractCompatibilityMetadataProvider(String spi, String providerId) {
|
||||
this.spi = spi;
|
||||
this.config = Config.scope(spi, providerId);
|
||||
}
|
||||
|
||||
abstract protected boolean isEnabled(Config.Scope scope);
|
||||
|
||||
@Override
|
||||
public Map<String, String> metadata() {
|
||||
if (!isEnabled(config))
|
||||
return Map.of();
|
||||
|
||||
Map<String, String> metadata = new HashMap<>(customMeta());
|
||||
configKeys().forEach(key -> {
|
||||
String value = config.get(key);
|
||||
if (value != null)
|
||||
metadata.put(key, value);
|
||||
});
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return spi;
|
||||
}
|
||||
|
||||
protected Map<String, String> customMeta() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
protected Stream<String> configKeys() {
|
||||
return Stream.of();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package org.keycloak.infinispan.compatibility;
|
||||
|
||||
import java.util.Map;
|
||||
import org.infinispan.commons.util.Version;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.MultiSiteUtils;
|
||||
import org.keycloak.compatibility.CompatibilityMetadataProvider;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
|
||||
/**
|
||||
* A {@link CompatibilityMetadataProvider} to provide metadata for the CLI options under the Caching category and
|
||||
* anything related to Infinispan.
|
||||
*/
|
||||
public class CachingCompatibilityMetadataProvider implements CompatibilityMetadataProvider {
|
||||
|
||||
public static final String ID = "caching";
|
||||
|
||||
@Override
|
||||
public Map<String, String> metadata() {
|
||||
return InfinispanUtils.isRemoteInfinispan() ?
|
||||
remoteInfinispanMetadata() :
|
||||
embeddedInfinispanMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
private static Map<String, String> remoteInfinispanMetadata() {
|
||||
return Map.of(
|
||||
"mode", "remote",
|
||||
"persistence", Boolean.toString(MultiSiteUtils.isPersistentSessionsEnabled())
|
||||
);
|
||||
}
|
||||
|
||||
private static Map<String, String> embeddedInfinispanMetadata() {
|
||||
return Map.of(
|
||||
"mode", "embedded",
|
||||
"persistence", Boolean.toString(Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)),
|
||||
"version", Version.getVersion(),
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.keycloak.infinispan.compatibility;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.infinispan.commons.util.Version;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.compatibility.AbstractCompatibilityMetadataProvider;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi;
|
||||
import org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProviderFactory;
|
||||
|
||||
public class CachingEmbeddedMetadataProvider extends AbstractCompatibilityMetadataProvider {
|
||||
|
||||
public CachingEmbeddedMetadataProvider() {
|
||||
super(CacheEmbeddedConfigProviderSpi.SPI_NAME, DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabled(Config.Scope scope) {
|
||||
return InfinispanUtils.isEmbeddedInfinispan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> customMeta() {
|
||||
return Map.of(
|
||||
"version", Version.getVersion(),
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> configKeys() {
|
||||
return Stream.of(DefaultCacheEmbeddedConfigProviderFactory.CONFIG, DefaultCacheEmbeddedConfigProviderFactory.STACK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.keycloak.infinispan.compatibility;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.compatibility.AbstractCompatibilityMetadataProvider;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi;
|
||||
import org.keycloak.spi.infinispan.impl.remote.DefaultCacheRemoteConfigProviderFactory;
|
||||
|
||||
public class CachingRemoteMetadataProvider extends AbstractCompatibilityMetadataProvider {
|
||||
|
||||
public CachingRemoteMetadataProvider() {
|
||||
super(CacheRemoteConfigProviderSpi.SPI_NAME, DefaultCacheRemoteConfigProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabled(Config.Scope scope) {
|
||||
return InfinispanUtils.isRemoteInfinispan();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Stream<String> configKeys() {
|
||||
return Stream.of(DefaultCacheRemoteConfigProviderFactory.HOSTNAME, DefaultCacheRemoteConfigProviderFactory.PORT);
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,10 @@ import org.keycloak.storage.configuration.ServerConfigStorageProvider;
|
||||
*/
|
||||
public class DefaultJGroupsCertificateProviderFactory implements JGroupsCertificateProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "default";
|
||||
|
||||
// config
|
||||
private static final String ENABLED = "enabled";
|
||||
public static final String ENABLED = "enabled";
|
||||
private static final String ROTATION = "rotation";
|
||||
private static final String KEYSTORE_PATH = "keystoreFile";
|
||||
private static final String KEYSTORE_PASSWORD = "keystorePassword";
|
||||
@@ -84,7 +86,7 @@ public class DefaultJGroupsCertificateProviderFactory implements JGroupsCertific
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.keycloak.jgroups.certificates;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.compatibility.AbstractCompatibilityMetadataProvider;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.spi.infinispan.JGroupsCertificateProviderSpi;
|
||||
|
||||
public class JGroupsCertificatesMetadataProvider extends AbstractCompatibilityMetadataProvider {
|
||||
|
||||
public JGroupsCertificatesMetadataProvider() {
|
||||
super(JGroupsCertificateProviderSpi.SPI_NAME, DefaultJGroupsCertificateProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabled(Config.Scope scope) {
|
||||
return InfinispanUtils.isEmbeddedInfinispan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> configKeys() {
|
||||
return Stream.of(DefaultJGroupsCertificateProviderFactory.ENABLED);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
org.keycloak.infinispan.compatibility.CachingCompatibilityMetadataProvider
|
||||
org.keycloak.infinispan.compatibility.CachingEmbeddedMetadataProvider
|
||||
org.keycloak.infinispan.compatibility.CachingRemoteMetadataProvider
|
||||
org.keycloak.jgroups.certificates.JGroupsCertificatesMetadataProvider
|
||||
@@ -202,7 +202,7 @@ final class CachingPropertyMappers {
|
||||
|
||||
return homeDir == null ?
|
||||
value :
|
||||
homeDir + File.separator + "conf" + File.separator + value;
|
||||
homeDir + (homeDir.endsWith(File.separator) ? "" : File.separator) + "conf" + File.separator + value;
|
||||
}
|
||||
|
||||
private static String getDefaultKeystorePathValue() {
|
||||
|
||||
@@ -17,38 +17,47 @@
|
||||
|
||||
package org.keycloak.it.cli.dist;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import java.io.File;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.keycloak.it.cli.dist.Util.createTempFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.compatibility.CompatibilityResult;
|
||||
import org.keycloak.compatibility.FeatureCompatibilityMetadataProvider;
|
||||
import org.keycloak.compatibility.KeycloakCompatibilityMetadataProvider;
|
||||
import org.keycloak.infinispan.compatibility.CachingCompatibilityMetadataProvider;
|
||||
import org.keycloak.it.junit5.extension.CLIResult;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
import org.keycloak.it.junit5.extension.RawDistOnly;
|
||||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
import org.keycloak.it.utils.RawKeycloakDistribution;
|
||||
import org.keycloak.jgroups.certificates.DefaultJGroupsCertificateProviderFactory;
|
||||
import org.keycloak.quarkus.runtime.cli.command.UpdateCompatibility;
|
||||
import org.keycloak.quarkus.runtime.cli.command.UpdateCompatibilityCheck;
|
||||
import org.keycloak.quarkus.runtime.cli.command.UpdateCompatibilityMetadata;
|
||||
import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi;
|
||||
import org.keycloak.spi.infinispan.JGroupsCertificateProviderSpi;
|
||||
import org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProviderFactory;
|
||||
import org.keycloak.spi.infinispan.impl.remote.DefaultCacheRemoteConfigProviderFactory;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.keycloak.it.cli.dist.Util.createTempFile;
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
|
||||
@DistributionTest
|
||||
@RawDistOnly(reason = "Requires creating JSON file to be available between containers")
|
||||
public class UpdateCommandDistTest {
|
||||
|
||||
private static final String DISABLE_FEATURE = "--features-disabled=rolling-updates";
|
||||
|
||||
@Test
|
||||
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, DISABLE_FEATURE})
|
||||
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, "--features-disabled=rolling-updates"})
|
||||
public void testFeatureNotEnabled(CLIResult cliResult) {
|
||||
cliResult.assertError("Unable to use this command. None of the versions of the feature 'rolling-updates' is enabled.");
|
||||
}
|
||||
@@ -74,14 +83,21 @@ public class UpdateCommandDistTest {
|
||||
@Test
|
||||
public void testCompatible(KeycloakDistribution distribution) throws IOException {
|
||||
var jsonFile = createTempFile("compatible", ".json");
|
||||
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(), "--cache-embedded-mtls-enabled", "true");
|
||||
result.assertMessage("Metadata:");
|
||||
assertEquals(0, result.exitCode());
|
||||
|
||||
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"));
|
||||
assertEquals(org.infinispan.commons.util.Version.getVersion(), info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME).get("version"));
|
||||
assertEquals(org.jgroups.Version.printVersion(), info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME).get("jgroupsVersion"));
|
||||
|
||||
var cacheMeta = info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME);
|
||||
assertTrue(cacheMeta.get(DefaultCacheEmbeddedConfigProviderFactory.CONFIG).endsWith("conf/cache-ispn.xml"));
|
||||
assertNull(cacheMeta.get(DefaultCacheEmbeddedConfigProviderFactory.STACK));
|
||||
|
||||
var jgroupsMeta = info.get(JGroupsCertificateProviderSpi.SPI_NAME);
|
||||
assertEquals("true", jgroupsMeta.get(DefaultJGroupsCertificateProviderFactory.ENABLED));
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.ROLLING.value());
|
||||
@@ -94,14 +110,9 @@ public class UpdateCommandDistTest {
|
||||
var jsonFile = createTempFile("wrong-versions", ".json");
|
||||
|
||||
// incompatible keycloak version
|
||||
var info = new HashMap<String, Map<String, String>>();
|
||||
info.put(KeycloakCompatibilityMetadataProvider.ID, Map.of("version", "0.0.0.Final"));
|
||||
info.put(CachingCompatibilityMetadataProvider.ID, Map.of(
|
||||
"version", org.infinispan.commons.util.Version.getVersion(),
|
||||
"persistence", "true",
|
||||
"mode", "embedded",
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
));
|
||||
var info = defaultMeta(distribution);
|
||||
info.get(KeycloakCompatibilityMetadataProvider.ID).put("version", "0.0.0.Final");
|
||||
|
||||
Profile.configure();
|
||||
info.put(FeatureCompatibilityMetadataProvider.ID, new FeatureCompatibilityMetadataProvider().metadata());
|
||||
JsonSerialization.mapper.writeValue(jsonFile, info);
|
||||
@@ -111,32 +122,119 @@ public class UpdateCommandDistTest {
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.version' is incompatible: 0.0.0.Final -> %s.".formatted(KeycloakCompatibilityMetadataProvider.ID, KeycloakCompatibilityMetadataProvider.ID, Version.VERSION));
|
||||
|
||||
// incompatible infinispan version
|
||||
info.put(KeycloakCompatibilityMetadataProvider.ID, Map.of("version", Version.VERSION));
|
||||
info.put(CachingCompatibilityMetadataProvider.ID, Map.of(
|
||||
"version", "0.0.0.Final",
|
||||
"persistence", "true",
|
||||
"mode", "embedded",
|
||||
"jgroupsVersion", org.jgroups.Version.printVersion()
|
||||
));
|
||||
info = defaultMeta(distribution);
|
||||
info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME).put("version", "0.0.0.Final");
|
||||
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
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.version' is incompatible: 0.0.0.Final -> %s.".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME, CacheEmbeddedConfigProviderSpi.SPI_NAME, 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"
|
||||
));
|
||||
info = defaultMeta(distribution);
|
||||
info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME).put("jgroupsVersion", "0.0.0.Final");
|
||||
JsonSerialization.mapper.writeValue(jsonFile, info);
|
||||
|
||||
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.jgroupsVersion' is incompatible: 0.0.0.Final -> %s.".formatted(CachingCompatibilityMetadataProvider.ID, CachingCompatibilityMetadataProvider.ID, org.jgroups.Version.printVersion()));
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.jgroupsVersion' is incompatible: 0.0.0.Final -> %s.".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME, CacheEmbeddedConfigProviderSpi.SPI_NAME, org.jgroups.Version.printVersion()));
|
||||
}
|
||||
|
||||
private String resolveConfigFile(KeycloakDistribution distribution, String... paths) {
|
||||
Path dist = distribution.unwrap(RawKeycloakDistribution.class).getDistPath();
|
||||
return Paths.get(dist.toString(), paths).toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheLocalChange(KeycloakDistribution distribution) throws IOException {
|
||||
var jsonFile = createTempFile("compatible", ".json");
|
||||
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath(), "--cache", "local");
|
||||
result.assertMessage("Metadata:");
|
||||
assertEquals(0, result.exitCode());
|
||||
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
assertTrue(info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME).get("configFile").endsWith("cache-local.xml"));
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), "--cache", "ispn");
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
|
||||
result.assertError("[%s] Rolling Update is not available. '%s.configFile' is incompatible: cache-local.xml -> %s.".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME, CacheEmbeddedConfigProviderSpi.SPI_NAME, resolveConfigFile(distribution, "conf", "cache-ispn.xml")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeCacheRemoteToEmbedded(KeycloakDistribution distribution) throws IOException {
|
||||
var jsonFile = createTempFile("compatible", ".json");
|
||||
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath(), "--features", "clusterless", "--cache-remote-host", "127.0.0.1");
|
||||
result.assertMessage("Metadata:");
|
||||
assertEquals(0, result.exitCode());
|
||||
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
assertEquals(Version.VERSION, info.get(KeycloakCompatibilityMetadataProvider.ID).get("version"));
|
||||
assertNull(info.get(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
assertNull(info.get(JGroupsCertificateProviderSpi.SPI_NAME));
|
||||
|
||||
var cacheMeta = info.get(CacheRemoteConfigProviderSpi.SPI_NAME);
|
||||
assertEquals("127.0.0.1", cacheMeta.get(DefaultCacheRemoteConfigProviderFactory.HOSTNAME));
|
||||
assertEquals("11222", cacheMeta.get(DefaultCacheRemoteConfigProviderFactory.PORT));
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.configFile' is incompatible: null".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.jgroupsVersion' is incompatible: null".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.version' is incompatible: null".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeCacheEmbeddedToRemote(KeycloakDistribution distribution) throws IOException {
|
||||
var jsonFile = createTempFile("compatible", ".json");
|
||||
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertMessage("Metadata:");
|
||||
assertEquals(0, result.exitCode());
|
||||
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
info.remove(FeatureCompatibilityMetadataProvider.ID);
|
||||
assertEquals(defaultMeta(distribution), info);
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), "--features", "clusterless", "--cache-remote-host", "127.0.0.1");
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.configFile' is incompatible:".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.jgroupsVersion' is incompatible:".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.version' is incompatible:".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeCacheStack(KeycloakDistribution distribution) throws IOException {
|
||||
var jsonFile = createTempFile("compatible", ".json");
|
||||
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath());
|
||||
result.assertMessage("Metadata:");
|
||||
assertEquals(0, result.exitCode());
|
||||
|
||||
var info = JsonSerialization.mapper.readValue(jsonFile, UpdateCompatibilityCheck.METADATA_TYPE_REF);
|
||||
info.remove(FeatureCompatibilityMetadataProvider.ID);
|
||||
assertEquals(defaultMeta(distribution), info);
|
||||
|
||||
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), "--cache-stack", "jdbc-ping-udp");
|
||||
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
|
||||
result.assertError("[%1$s] Rolling Update is not available. '%1$s.stack' is incompatible: null -> jdbc-ping-udp".formatted(CacheEmbeddedConfigProviderSpi.SPI_NAME));
|
||||
}
|
||||
|
||||
private Map<String, Map<String, String>> defaultMeta(KeycloakDistribution distribution) {
|
||||
Map<String, String> keycloak = new HashMap<>(1);
|
||||
keycloak.put("version", Version.VERSION);
|
||||
|
||||
Map<String, Map<String, String>> m = new HashMap<>();
|
||||
m.put(KeycloakCompatibilityMetadataProvider.ID, keycloak);
|
||||
m.put(CacheEmbeddedConfigProviderSpi.SPI_NAME, embeddedCachingMeta(distribution));
|
||||
m.put(JGroupsCertificateProviderSpi.SPI_NAME, Map.of(
|
||||
"enabled", "true"
|
||||
));
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, String> embeddedCachingMeta(KeycloakDistribution distribution) {
|
||||
Map<String, String> m = new HashMap<>();
|
||||
m.put("version", org.infinispan.commons.util.Version.getVersion());
|
||||
m.put("jgroupsVersion", org.jgroups.Version.printVersion());
|
||||
m.put("configFile", resolveConfigFile(distribution, "conf", "cache-ispn.xml"));
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ public class KeycloakCompatibilityMetadataProvider implements CompatibilityMetad
|
||||
public static final String VERSION_KEY = "version";
|
||||
private final String version;
|
||||
|
||||
// Constructor required for ServiceLoader
|
||||
@SuppressWarnings("unused")
|
||||
public KeycloakCompatibilityMetadataProvider() {
|
||||
this(Version.VERSION);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.keycloak.compatibility;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -9,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.infinispan.commons.util.ReflectionUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
@@ -122,6 +124,32 @@ public class FeatureCompatibilityMetadataProviderTest extends AbstractCompatibil
|
||||
assertCompatibility(CompatibilityResult.ExitCode.RECREATE, provider.isCompatible(v1Meta));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("addedFeatures")
|
||||
public void testAddedFeature(CompatibilityResult.ExitCode exitCode, Profile.Feature featureToAdd) {
|
||||
Profile.configure();
|
||||
FeatureCompatibilityMetadataProvider provider = new FeatureCompatibilityMetadataProvider();
|
||||
Map<String, String> other = provider.metadata();
|
||||
|
||||
// Remove an existing Feature from the profile to emulate a new Profile.Feature being added in a subsequent KC version
|
||||
Profile instance = Profile.getInstance();
|
||||
Map<Profile.Feature, Boolean> features = new HashMap<>(instance.getFeatures());
|
||||
features.remove(featureToAdd);
|
||||
Field featuresField = ReflectionUtil.getField("features", Profile.class);
|
||||
featuresField.setAccessible(true);
|
||||
ReflectionUtil.setField(instance, featuresField, features);
|
||||
assertCompatibility(exitCode, provider.isCompatible(other));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> addedFeatures() {
|
||||
return Stream.of(
|
||||
Arguments.of(CompatibilityResult.ExitCode.ROLLING, Profile.Feature.IMPERSONATION),
|
||||
Arguments.of(CompatibilityResult.ExitCode.RECREATE, Profile.Feature.PERSISTENT_USER_SESSIONS),
|
||||
// Expect a RECREATE as the Feature has the ROLLING_NO_UPGRADE policy
|
||||
Arguments.of(CompatibilityResult.ExitCode.RECREATE, Profile.Feature.LOGIN_V2)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("removedFeatures")
|
||||
public void testRemovedFeature(CompatibilityResult.ExitCode exitCode, Profile.FeatureUpdatePolicy updatePolicy) {
|
||||
|
||||
Reference in New Issue
Block a user