> dependsOn() {
- return Set.of(JpaConnectionProvider.class, CacheRemoteConfigProvider.class);
+ return Set.of(CacheRemoteConfigProvider.class, CacheEmbeddedConfigProvider.class);
+ }
+
+ @Override
+ public void onEvent(ProviderEvent event) {
+ if (event instanceof PostMigrationEvent pme) {
+ KeycloakModelUtils.runJobInTransaction(pme.getFactory(), this::registerSystemWideListeners);
+ }
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 38a0f303f0e..8e0771beba5 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -114,6 +114,21 @@ public interface InfinispanConnectionProvider extends Provider {
String[] ALL_CACHES_NAME = Stream.concat(Arrays.stream(LOCAL_CACHE_NAMES), Arrays.stream(CLUSTERED_CACHE_NAMES)).toArray(String[]::new);
+ String[] LOCAL_MAX_COUNT_CACHES = new String[]{
+ AUTHORIZATION_CACHE_NAME,
+ CRL_CACHE_NAME,
+ KEYS_CACHE_NAME,
+ REALM_CACHE_NAME,
+ USER_CACHE_NAME
+ };
+
+ String[] CLUSTERED_MAX_COUNT_CACHES = new String[]{
+ CLIENT_SESSION_CACHE_NAME,
+ OFFLINE_USER_SESSION_CACHE_NAME,
+ OFFLINE_CLIENT_SESSION_CACHE_NAME,
+ USER_SESSION_CACHE_NAME
+ };
+
/**
*
* Effectively the same as {@link InfinispanConnectionProvider#getCache(String, boolean)} with createIfAbsent set to {@code true}
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java
index 02a364d2d65..95f8e03d721 100644
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java
@@ -17,7 +17,10 @@
package org.keycloak.connections.infinispan;
-import org.infinispan.commons.dataconversion.MediaType;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
import org.infinispan.commons.time.TimeService;
import org.infinispan.commons.util.FileLookup;
import org.infinispan.commons.util.FileLookupFactory;
@@ -35,10 +38,7 @@ import org.jboss.logging.Logger;
import org.jgroups.JChannel;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
-
-import java.time.Instant;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
+import org.keycloak.spi.infinispan.impl.embedded.CacheConfigurator;
/**
* @author Marek Posolda
@@ -56,6 +56,10 @@ public class InfinispanUtil {
private static final Object CHANNEL_INIT_SYNCHRONIZER = new Object();
+ /**
+ * @deprecated to be removed without replacement.
+ */
+ @Deprecated(since = "26.3", forRemoval = true)
public static void configureTransport(GlobalConfigurationBuilder gcb, String nodeName, String siteName, String jgroupsUdpMcastAddr,
String jgroupsBindAddr, String jgroupsConfigPath) {
if (nodeName == null) {
@@ -115,20 +119,20 @@ public class InfinispanUtil {
}
}
+ /**
+ * @deprecated to be removed. Use {@link CacheConfigurator#createCacheConfigurationBuilder()}.
+ */
+ @Deprecated(since = "26.3", forRemoval = true)
public static ConfigurationBuilder createCacheConfigurationBuilder() {
- ConfigurationBuilder builder = new ConfigurationBuilder();
-
- // need to force the encoding to application/x-java-object to avoid unnecessary conversion of keys/values. See WFLY-14356.
- builder.encoding().mediaType(MediaType.APPLICATION_OBJECT_TYPE);
-
- // needs to be disabled if transaction is enabled
- builder.simpleCache(true);
-
- return builder;
+ return CacheConfigurator.createCacheConfigurationBuilder();
}
+ /**
+ * @deprecated to be removed without replacement.
+ */
+ @Deprecated(since = "26.3", forRemoval = true)
public static ConfigurationBuilder getActionTokenCacheConfig() {
- ConfigurationBuilder cb = createCacheConfigurationBuilder();
+ var cb = CacheConfigurator.createCacheConfigurationBuilder();
cb.memory()
.whenFull(EvictionStrategy.MANUAL)
@@ -140,19 +144,26 @@ public class InfinispanUtil {
return cb;
}
+ /**
+ * @deprecated to be removed. Use {@link CacheConfigurator#getCrlCacheConfig()}.
+ */
+ @Deprecated(since = "26.3", forRemoval = true)
public static ConfigurationBuilder getCrlCacheConfig() {
- var builder = createCacheConfigurationBuilder();
+ return CacheConfigurator.getCrlCacheConfig();
+ }
- builder.memory()
- .whenFull(EvictionStrategy.REMOVE)
- .maxCount(InfinispanConnectionProvider.CRL_CACHE_DEFAULT_MAX);
-
- return builder;
+ /**
+ * @deprecated to be removed. Use {@link CacheConfigurator#getRevisionCacheConfig(long)}.
+ */
+ @Deprecated(since = "26.3", forRemoval = true)
+ public static ConfigurationBuilder getRevisionCacheConfig(long maxEntries) {
+ return CacheConfigurator.getRevisionCacheConfig(maxEntries);
}
/**
* Replaces the {@link TimeService} in infinispan with the one that respects Keycloak {@link Time}.
- * @param cacheManager
+ *
+ * @param cacheManager The {@link EmbeddedCacheManager} to inject the Keycloak {@link Time}.
* @return Runnable to revert replacement of the infinispan time service
*/
public static Runnable setTimeServiceToKeycloakTime(EmbeddedCacheManager cacheManager) {
@@ -172,14 +183,13 @@ public class InfinispanUtil {
/**
* Forked from org.infinispan.test.TestingUtil class
- *
+ *
* Replaces a component in a running cache manager (global component registry).
*
- * @param cacheMgr cache in which to replace component
+ * @param cacheMgr cache in which to replace component
* @param componentType component type of which to replace
* @param replacementComponent new instance
* @param rewire if true, ComponentRegistry.rewire() is called after replacing.
- *
* @return the original component that was replaced
*/
private static T replaceComponent(EmbeddedCacheManager cacheMgr, Class componentType, T replacementComponent, boolean rewire) {
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/TopologyInfo.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/TopologyInfo.java
index 56bf4feaf42..021de2898cf 100644
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/TopologyInfo.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/TopologyInfo.java
@@ -17,6 +17,10 @@
package org.keycloak.connections.infinispan;
+import java.net.InetSocketAddress;
+import java.security.SecureRandom;
+import java.util.Objects;
+
import org.infinispan.Cache;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.factories.GlobalComponentRegistry;
@@ -31,10 +35,6 @@ import org.jgroups.stack.IpAddress;
import org.jgroups.util.NameCache;
import org.keycloak.Config;
-import java.net.InetSocketAddress;
-import java.security.SecureRandom;
-import java.util.Objects;
-
/**
* @author Marek Posolda
*/
@@ -44,7 +44,6 @@ public class TopologyInfo {
// Node name used in clustered environment. This typically points to "jboss.node.name" . If "jboss.node.name" is not set, it is randomly generated
- // name
private final String myNodeName;
// Used just if "site" is configured (typically in multi-site environment). Otherwise null
@@ -53,67 +52,37 @@ public class TopologyInfo {
private final boolean isGeneratedNodeName;
+ /**
+ * @deprecated Use {@link #TopologyInfo(EmbeddedCacheManager)} instead.
+ */
+ @Deprecated(since = "26.3", forRemoval = true)
public TopologyInfo(EmbeddedCacheManager cacheManager, Config.Scope config, boolean embedded, String providerId) {
- String siteName;
- String nodeName;
- boolean isGeneratedNodeName = false;
-
- if (System.getProperty(InfinispanConnectionProvider.JBOSS_SITE_NAME) != null) {
- throw new IllegalArgumentException(
- String.format("System property %s is in use. Use --spi-connections-infinispan-%s-site-name config option instead",
- InfinispanConnectionProvider.JBOSS_SITE_NAME, providerId));
- }
-
- if (!embedded) {
- var addr = cacheManager.getAddress();
- if (addr != null) {
- nodeName = addr.toString();
- siteName = cacheManager.getCacheManagerConfiguration().transport().siteId();
- if (siteName == null) {
- siteName = config.get("siteName");
- }
- } else {
- nodeName = System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME);
- siteName = config.get("siteName");
- }
- if (nodeName == null || nodeName.equals("localhost")) {
- isGeneratedNodeName = true;
- nodeName = generateNodeName();
- }
- } else {
- boolean clustered = config.getBoolean("clustered", false);
-
- nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
- if (nodeName != null && nodeName.isEmpty()) {
- nodeName = null;
- }
-
- siteName = config.get("siteName");
- if (siteName != null && siteName.isEmpty()) {
- siteName = null;
- }
-
- if (nodeName == null) {
- if (!clustered) {
- isGeneratedNodeName = true;
- nodeName = generateNodeName();
- } else {
- throw new IllegalStateException("You must set jboss.node.name if you use clustered mode for InfinispanConnectionProvider");
- }
- }
- }
-
- this.myNodeName = nodeName;
- this.mySiteName = siteName;
- this.isGeneratedNodeName = isGeneratedNodeName;
+ this(cacheManager);
}
+ public TopologyInfo(EmbeddedCacheManager cacheManager) {
+ var transportConfig = cacheManager.getCacheManagerConfiguration().transport();
+ var transport = GlobalComponentRegistry.componentOf(cacheManager, Transport.class);
- private String generateNodeName() {
+ if (transport == null) {
+ // non-clustered mode
+ // if the user sets a node name, use it; otherwise, generate the node name.
+ var nodeName = transportConfig.nodeName();
+ this.isGeneratedNodeName = nodeName == null || nodeName.isEmpty();
+ this.myNodeName = isGeneratedNodeName ? generateNodeName() : nodeName;
+ } else {
+ // clustered mode
+ // returns the node name configured; if not set, the node name generated by JGroups.
+ this.myNodeName = transport.localNodeName();
+ this.isGeneratedNodeName = false;
+ }
+ this.mySiteName = transportConfig.siteId();
+ }
+
+ private static String generateNodeName() {
return InfinispanConnectionProvider.NODE_PREFIX + new SecureRandom().nextInt(1000000);
}
-
public String getMyNodeName() {
return myNodeName;
}
@@ -122,13 +91,11 @@ public class TopologyInfo {
return mySiteName;
}
-
@Override
public String toString() {
return String.format("Node name: %s, Site name: %s", myNodeName, mySiteName);
}
-
/**
* True if I am primary owner of the key in case of distributed caches. In case of local caches, always return true
*/
diff --git a/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsConfigurator.java b/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsConfigurator.java
deleted file mode 100644
index 7d032070e5a..00000000000
--- a/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsConfigurator.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2025 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.jgroups;
-
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
-import org.jboss.logging.Logger;
-import org.keycloak.Config;
-import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
-import org.keycloak.infinispan.util.InfinispanUtils;
-import org.keycloak.jgroups.impl.JGroupsJdbcPingStackConfigurator;
-import org.keycloak.jgroups.impl.JpaJGroupsTlsConfigurator;
-import org.keycloak.models.KeycloakSession;
-
-/**
- * Configures the JGroups stacks before starting Infinispan.
- */
-public class JGroupsConfigurator {
-
- public static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
-
- private final ConfigurationBuilderHolder holder;
- private final List stackConfiguratorList;
-
- private JGroupsConfigurator(ConfigurationBuilderHolder holder, List stackConfiguratorList) {
- this.holder = holder;
- this.stackConfiguratorList = stackConfiguratorList;
- }
-
- private static void createJdbcPingConfigurator(ConfigurationBuilderHolder holder, List configurator) {
- var stackXmlAttribute = JGroupsUtil.transportStackOf(holder);
- if (stackXmlAttribute.isModified() && !isJdbcPingStack(stackXmlAttribute.get())) {
- logger.debugf("Custom stack configured (%s). JDBC_PING discovery disabled.", stackXmlAttribute.get());
- return;
- }
- logger.debug("JDBC_PING discovery enabled.");
- if (!stackXmlAttribute.isModified()) {
- // defaults to jdbc-ping
- JGroupsUtil.transportOf(holder).stack("jdbc-ping");
- }
- configurator.add(JGroupsJdbcPingStackConfigurator.INSTANCE);
- }
-
- private static boolean isJdbcPingStack(String stackName) {
- return "jdbc-ping".equals(stackName) || "jdbc-ping-udp".equals(stackName);
- }
-
- private static void createTlsConfigurator(List configurator) {
- configurator.add(JpaJGroupsTlsConfigurator.INSTANCE);
- }
-
- private static boolean isLocal(ConfigurationBuilderHolder holder) {
- return JGroupsUtil.transportOf(holder).getTransport() == null;
- }
-
- public static JGroupsConfigurator create(ConfigurationBuilderHolder holder) {
- if (InfinispanUtils.isRemoteInfinispan() || isLocal(holder)) {
- logger.debug("Multi Site or local mode. Skipping JGroups configuration.");
- return new JGroupsConfigurator(holder, List.of());
- }
- // Configure stack from CLI options to Global Configuration
- var stack = Config.scope(InfinispanConnectionSpi.SPI_NAME, "quarkus").get("stack");
- if (stack != null) {
- JGroupsUtil.transportOf(holder).stack(stack);
- }
- var configurator = new ArrayList(2);
- createJdbcPingConfigurator(holder, configurator);
- createTlsConfigurator(configurator);
- return new JGroupsConfigurator(holder, List.copyOf(configurator));
- }
-
- /**
- * @return The {@link ConfigurationBuilderHolder} with the current Infinispan configuration.
- */
- public ConfigurationBuilderHolder holder() {
- return holder;
- }
-
- /**
- * @return {@code true} if Keycloak is run in local mode (development mode for example) and JGroups won't be used.
- */
- public boolean isLocal() {
- return isLocal(holder);
- }
-
- /**
- * Configures the JGroups stack.
- *
- * @param session The {@link KeycloakSession}.
- */
- public void configure(KeycloakSession session) {
- if (InfinispanUtils.isRemoteInfinispan() || isLocal()) {
- return;
- }
- stackConfiguratorList.forEach(jGroupsStackConfigurator -> jGroupsStackConfigurator.configure(holder, session));
- JGroupsUtil.warnDeprecatedStack(holder);
- }
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsUtil.java b/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsUtil.java
deleted file mode 100644
index 90d7f253cb3..00000000000
--- a/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsUtil.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2025 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.jgroups;
-
-import org.infinispan.commons.configuration.attributes.Attribute;
-import org.infinispan.configuration.global.TransportConfigurationBuilder;
-import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
-import org.jgroups.protocols.TCP_NIO2;
-import org.jgroups.protocols.UDP;
-
-import static org.infinispan.configuration.global.TransportConfiguration.STACK;
-
-public final class JGroupsUtil {
-
- private JGroupsUtil() {
- }
-
- public static TransportConfigurationBuilder transportOf(ConfigurationBuilderHolder holder) {
- return holder.getGlobalConfigurationBuilder().transport();
- }
-
- public static Attribute transportStackOf(ConfigurationBuilderHolder holder) {
- var transport = transportOf(holder);
- assert transport != null;
- return transport.attributes().attribute(STACK);
- }
-
- public static void warnDeprecatedStack(ConfigurationBuilderHolder holder) {
- var stackName = transportStackOf(holder).get();
- switch (stackName) {
- case "jdbc-ping-udp":
- case "tcp":
- case "udp":
- case "azure":
- case "ec2":
- case "google":
- JGroupsConfigurator.logger.warnf("Stack '%s' is deprecated. We recommend to use 'jdbc-ping' instead", stackName);
- }
- }
-
- public static void validateTlsAvailable(ConfigurationBuilderHolder holder) {
- var stackName = transportStackOf(holder).get();
- if (stackName == null) {
- // unable to validate
- return;
- }
- var config = transportOf(holder).build();
- for (var protocol : config.transport().jgroups().configurator(stackName).getProtocolStack()) {
- var name = protocol.getProtocolName();
- if (name.equals(UDP.class.getSimpleName()) ||
- name.equals(UDP.class.getName()) ||
- name.equals(TCP_NIO2.class.getSimpleName()) ||
- name.equals(TCP_NIO2.class.getName())) {
- throw new RuntimeException("Cache TLS is not available with protocol " + name);
- }
- }
-
- }
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/jgroups/impl/JGroupsJdbcPingStackConfigurator.java b/model/infinispan/src/main/java/org/keycloak/jgroups/impl/JGroupsJdbcPingStackConfigurator.java
deleted file mode 100644
index 06c80e9349b..00000000000
--- a/model/infinispan/src/main/java/org/keycloak/jgroups/impl/JGroupsJdbcPingStackConfigurator.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2025 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.jgroups.impl;
-
-import java.util.List;
-import java.util.Map;
-
-import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
-import org.infinispan.remoting.transport.jgroups.EmbeddedJGroupsChannelConfigurator;
-import org.jgroups.conf.ClassConfigurator;
-import org.jgroups.conf.ProtocolConfiguration;
-import org.jgroups.protocols.JDBC_PING2;
-import org.jgroups.stack.Protocol;
-import org.keycloak.connections.jpa.JpaConnectionProvider;
-import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
-import org.keycloak.connections.jpa.util.JpaUtils;
-import org.keycloak.jgroups.JGroupsConfigurator;
-import org.keycloak.jgroups.JGroupsStackConfigurator;
-import org.keycloak.jgroups.JGroupsUtil;
-import org.keycloak.models.KeycloakSession;
-
-/**
- * JGroups discovery configuration using {@link JDBC_PING2}.
- */
-public class JGroupsJdbcPingStackConfigurator implements JGroupsStackConfigurator {
-
- public static final JGroupsStackConfigurator INSTANCE = new JGroupsJdbcPingStackConfigurator();
-
- private JGroupsJdbcPingStackConfigurator() {}
-
- static {
- // Use custom Keycloak JDBC_PING implementation to use the connection from JpaConnectionProviderFactory
- // The id 1025 follows this instruction: https://github.com/belaban/JGroups/blob/38219e9ec1c629fa2f7929e3b53d1417d8e60b61/conf/jg-protocol-ids.xml#L85
- ClassConfigurator.addProtocol((short) 1025, KEYCLOAK_JDBC_PING2.class);
- }
-
- @Override
- public void configure(ConfigurationBuilderHolder holder, KeycloakSession session) {
- var em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
- var stackName = JGroupsUtil.transportStackOf(holder).get();
- var isUdp = stackName.endsWith("udp");
- var tableName = JpaUtils.getTableNameForNativeQuery("JGROUPS_PING", em);
- var stack = getProtocolConfigurations(tableName, isUdp ? "PING" : "MPING");
- var connectionFactory = (JpaConnectionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
- holder.addJGroupsStack(new JpaFactoryAwareJGroupsChannelConfigurator(stackName, stack,connectionFactory, isUdp), null);
-
- JGroupsUtil.transportOf(holder).stack(stackName);
- JGroupsConfigurator.logger.info("JGroups JDBC_PING discovery enabled.");
- }
-
- private static List getProtocolConfigurations(String tableName, String discoveryProtocol) {
- var attributes = Map.of(
- // Leave initialize_sql blank as table is already created by Keycloak
- "initialize_sql", "",
- // Explicitly specify clear and select_all SQL to ensure "cluster_name" column is used, as the default
- // "cluster" cannot be used with Oracle DB as it's a reserved word.
- "clear_sql", String.format("DELETE from %s WHERE cluster_name=?", tableName),
- "delete_single_sql", String.format("DELETE from %s WHERE address=?", tableName),
- "insert_single_sql", String.format("INSERT INTO %s values (?, ?, ?, ?, ?)", tableName),
- "select_all_pingdata_sql", String.format("SELECT address, name, ip, coord FROM %s WHERE cluster_name=?", tableName),
- "remove_all_data_on_view_change", "true",
- "register_shutdown_hook", "false",
- "stack.combine", "REPLACE",
- "stack.position", discoveryProtocol
- );
- return List.of(new ProtocolConfiguration(KEYCLOAK_JDBC_PING2.class.getName(), attributes));
- }
-
- private static class JpaFactoryAwareJGroupsChannelConfigurator extends EmbeddedJGroupsChannelConfigurator {
-
- private final JpaConnectionProviderFactory factory;
-
- public JpaFactoryAwareJGroupsChannelConfigurator(String name, List stack, JpaConnectionProviderFactory factory, boolean isUdp) {
- super(name, stack, null, isUdp ? "udp" : "tcp");
- this.factory = factory;
- }
-
- @Override
- public void afterCreation(Protocol protocol) {
- super.afterCreation(protocol);
- if (protocol instanceof KEYCLOAK_JDBC_PING2 kcPing) {
- kcPing.setJpaConnectionProviderFactory(factory);
- }
- }
- }
-
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/jgroups/impl/JpaJGroupsTlsConfigurator.java b/model/infinispan/src/main/java/org/keycloak/jgroups/impl/JpaJGroupsTlsConfigurator.java
deleted file mode 100644
index 218029ee240..00000000000
--- a/model/infinispan/src/main/java/org/keycloak/jgroups/impl/JpaJGroupsTlsConfigurator.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2025 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.jgroups.impl;
-
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-
-import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
-import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
-import org.jgroups.util.DefaultSocketFactory;
-import org.jgroups.util.SocketFactory;
-import org.keycloak.infinispan.module.configuration.global.KeycloakConfigurationBuilder;
-import org.keycloak.jgroups.JGroupsConfigurator;
-import org.keycloak.jgroups.JGroupsStackConfigurator;
-import org.keycloak.jgroups.JGroupsUtil;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.spi.infinispan.JGroupsCertificateProvider;
-import org.keycloak.storage.configuration.ServerConfigStorageProvider;
-
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.TrustManager;
-
-/**
- * JGroups mTLS configuration using certificates stored by {@link ServerConfigStorageProvider}.
- */
-public class JpaJGroupsTlsConfigurator implements JGroupsStackConfigurator {
-
- private static final String TLS_PROTOCOL_VERSION = "TLSv1.3";
- private static final String TLS_PROTOCOL = "TLS";
- public static final JpaJGroupsTlsConfigurator INSTANCE = new JpaJGroupsTlsConfigurator();
-
-
- @Override
- public void configure(ConfigurationBuilderHolder holder, KeycloakSession session) {
- var kcConfig = holder.getGlobalConfigurationBuilder().addModule(KeycloakConfigurationBuilder.class);
- kcConfig.setKeycloakSessionFactory(session.getKeycloakSessionFactory());
- var provider = session.getProvider(JGroupsCertificateProvider.class);
- if (provider == null || !provider.isEnabled()) {
- return;
- }
- var factory = createSocketFactory(provider);
- JGroupsUtil.transportOf(holder).addProperty(JGroupsTransport.SOCKET_FACTORY, factory);
- JGroupsUtil.validateTlsAvailable(holder);
- JGroupsConfigurator.logger.info("JGroups Encryption enabled (mTLS).");
- }
-
-
- private static SocketFactory createSocketFactory(JGroupsCertificateProvider provider) {
- try {
- var sslContext = SSLContext.getInstance(TLS_PROTOCOL);
- sslContext.init(new KeyManager[]{provider.keyManager()}, new TrustManager[]{provider.trustManager()}, null);
- return createFromContext(sslContext);
- } catch (KeyManagementException | NoSuchAlgorithmException e) {
- // we should have valid certificates and keys.
- throw new RuntimeException(e);
- }
- }
-
- private static SocketFactory createFromContext(SSLContext context) {
- DefaultSocketFactory socketFactory = new DefaultSocketFactory(context);
- final SSLParameters serverParameters = new SSLParameters();
- serverParameters.setProtocols(new String[]{TLS_PROTOCOL_VERSION});
- serverParameters.setNeedClientAuth(true);
- socketFactory.setServerSocketConfigurator(socket -> ((SSLServerSocket) socket).setSSLParameters(serverParameters));
- return socketFactory;
- }
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/jgroups/impl/KEYCLOAK_JDBC_PING2.java b/model/infinispan/src/main/java/org/keycloak/jgroups/protocol/KEYCLOAK_JDBC_PING2.java
similarity index 73%
rename from model/infinispan/src/main/java/org/keycloak/jgroups/impl/KEYCLOAK_JDBC_PING2.java
rename to model/infinispan/src/main/java/org/keycloak/jgroups/protocol/KEYCLOAK_JDBC_PING2.java
index 4fe70eb1f9c..202a3b22913 100644
--- a/model/infinispan/src/main/java/org/keycloak/jgroups/impl/KEYCLOAK_JDBC_PING2.java
+++ b/model/infinispan/src/main/java/org/keycloak/jgroups/protocol/KEYCLOAK_JDBC_PING2.java
@@ -1,4 +1,21 @@
-package org.keycloak.jgroups.impl;
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.jgroups.protocol;
import org.jgroups.protocols.JDBC_PING2;
import org.jgroups.protocols.PingData;
@@ -43,7 +60,7 @@ public class KEYCLOAK_JDBC_PING2 extends JDBC_PING2 {
throw e;
} finally {
if (isAutocommit) {
- connection.setAutoCommit(isAutocommit);
+ connection.setAutoCommit(true);
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsStackConfigurator.java b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProvider.java
similarity index 54%
rename from model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsStackConfigurator.java
rename to model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProvider.java
index 48f438c3905..0198fb0f0f5 100644
--- a/model/infinispan/src/main/java/org/keycloak/jgroups/JGroupsStackConfigurator.java
+++ b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProvider.java
@@ -15,22 +15,27 @@
* limitations under the License.
*/
-package org.keycloak.jgroups;
+package org.keycloak.spi.infinispan;
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
-import org.keycloak.models.KeycloakSession;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.keycloak.provider.Provider;
/**
- * Interface to configure a JGroups Stack before Keycloak starts the embedded Infinispan.
+ * A provider to create the {@link ConfigurationBuilderHolder} to configure the {@link EmbeddedCacheManager}.
*/
-public interface JGroupsStackConfigurator {
+public interface CacheEmbeddedConfigProvider extends Provider {
/**
- * Configures the stack in {@code holder}.
+ * The {@link ConfigurationBuilderHolder} whit the {@link EmbeddedCacheManager} configuration. It must not be
+ * {@code null}.
*
- * @param holder The Infinispan {@link ConfigurationBuilderHolder}.
- * @param session The current {@link KeycloakSession}. It may be {@code null};
+ * @return The {@link ConfigurationBuilderHolder} whit the {@link EmbeddedCacheManager} configuration.
*/
- void configure(ConfigurationBuilderHolder holder, KeycloakSession session);
+ ConfigurationBuilderHolder configuration();
+ @Override
+ default void close() {
+ //no-op
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProviderFactory.java
new file mode 100644
index 00000000000..e2110a3bc6f
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.spi.infinispan;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * The {@link ProviderFactory} to build {@link CacheEmbeddedConfigProvider}.
+ */
+public interface CacheEmbeddedConfigProviderFactory extends ProviderFactory {
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProviderSpi.java b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProviderSpi.java
new file mode 100644
index 00000000000..597ad03e8eb
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/CacheEmbeddedConfigProviderSpi.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.spi.infinispan;
+
+import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.keycloak.provider.Spi;
+
+/**
+ * The {@link Spi} implementation for the {@link CacheEmbeddedConfigProviderFactory} and
+ * {@link CacheEmbeddedConfigProvider}.
+ *
+ * It provides the {@link ConfigurationBuilderHolder} to configure the {@link EmbeddedCacheManager}.
+ */
+public class CacheEmbeddedConfigProviderSpi implements Spi {
+
+ public static final String SPI_NAME = "cacheEmbedded";
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return SPI_NAME;
+ }
+
+ @Override
+ public Class getProviderClass() {
+ return CacheEmbeddedConfigProvider.class;
+ }
+
+ @Override
+ public Class getProviderFactoryClass() {
+ return CacheEmbeddedConfigProviderFactory.class;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/CacheConfigurator.java b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/CacheConfigurator.java
new file mode 100644
index 00000000000..f84fd30fc3e
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/CacheConfigurator.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.spi.infinispan.impl.embedded;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.infinispan.commons.dataconversion.MediaType;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.HashConfiguration;
+import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
+import org.infinispan.eviction.EvictionStrategy;
+import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_DEFAULT_MAX;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOCAL_CACHE_NAMES;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOCAL_MAX_COUNT_CACHES;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.REALM_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.WORK_CACHE_NAME;
+
+/**
+ * Utility class related to the Infinispan cache configuration.
+ *
+ * This class contains methods to configure caches based on the SPI configuration options, and it provides cache
+ * configuration defaults.
+ */
+public final class CacheConfigurator {
+
+ private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Map with the default cache configuration if the cache is not present in the XML.
+ private static final Map> DEFAULT_CONFIGS = Map.of(CRL_CACHE_NAME, CacheConfigurator::getCrlCacheConfig);
+ private static final Supplier TO_NULL = () -> null;
+ private static final String MAX_COUNT_SUFFIX = "MaxCount";
+
+ private CacheConfigurator() {
+ }
+
+ /**
+ * Configures the Infinispan local caches used by Keycloak (e.g., for realm or user data) using the provided
+ * Keycloak configuration.
+ *
+ * @param keycloakConfig The Keycloak configuration.
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches will be defined.
+ * @throws IllegalStateException if an Infinispan cache is not defined. This could indicate a missing or incorrect
+ * configuration.
+ */
+ public static void configureLocalCaches(Config.Scope keycloakConfig, ConfigurationBuilderHolder holder) {
+ logger.debug("Configuring embedded local caches");
+ // configure local caches except revision caches
+ configureCacheMaxCount(keycloakConfig, holder, Arrays.stream(LOCAL_MAX_COUNT_CACHES));
+ // configure revision caches
+ configureRevisionCache(holder, REALM_CACHE_NAME, REALM_REVISIONS_CACHE_NAME, REALM_REVISIONS_CACHE_DEFAULT_MAX);
+ configureRevisionCache(holder, USER_CACHE_NAME, USER_REVISIONS_CACHE_NAME, USER_REVISIONS_CACHE_DEFAULT_MAX);
+ configureRevisionCache(holder, AUTHORIZATION_CACHE_NAME, AUTHORIZATION_REVISIONS_CACHE_NAME, AUTHORIZATION_REVISIONS_CACHE_DEFAULT_MAX);
+ // check all caches are defined
+ checkCachesExist(holder, Arrays.stream(LOCAL_CACHE_NAMES));
+ }
+
+ /**
+ * Applies the default Infinispan cache configuration to the {@code holder}, if the cache is not present.
+ *
+ * Each cache may have its own default configuration.
+ *
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches will be defined.
+ */
+ public static void applyDefaultConfiguration(ConfigurationBuilderHolder holder) {
+ var configs = holder.getNamedConfigurationBuilders();
+ for (var name : InfinispanConnectionProvider.ALL_CACHES_NAME) {
+ configs.computeIfAbsent(name, cacheName -> DEFAULT_CONFIGS.getOrDefault(cacheName, TO_NULL).get());
+ }
+ }
+
+ /**
+ * Verifies that all the {@code caches} are defined in the {@code holder}.
+ *
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches are configured.
+ * @param caches The {@link Stream} containing the names of the caches to check.
+ * @throws IllegalStateException if one or more Infinispan caches from the provided {@code caches} stream are not
+ * defined in the {@code holder}. This could indicate a missing or incorrect
+ * configuration for those specific caches.
+ */
+ public static void checkCachesExist(ConfigurationBuilderHolder holder, Stream caches) {
+ for (var it = caches.iterator(); it.hasNext(); ) {
+ var cache = it.next();
+ var builder = holder.getNamedConfigurationBuilders().get(cache);
+ if (builder == null) {
+ throw cacheNotFound(cache);
+ }
+ }
+ }
+
+ /**
+ * Validates that the "work" cache is present in the {@code holder} and has a valid configuration.
+ *
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches are configured.
+ * @throws IllegalStateException if the "work" cache is not found in the holder.
+ * @throws RuntimeException if the "work" cache has an invalid configuration. This could include an incorrect
+ * settings that would prevent the cache from functioning correctly.
+ */
+ public static void validateWorkCacheConfiguration(ConfigurationBuilderHolder holder) {
+ logger.debugf("Validating %s cache configuration", WORK_CACHE_NAME);
+ var cacheBuilder = holder.getNamedConfigurationBuilders().get(WORK_CACHE_NAME);
+ if (cacheBuilder == null) {
+ throw cacheNotFound(WORK_CACHE_NAME);
+ }
+ if (holder.getGlobalConfigurationBuilder().cacheContainer().transport().getTransport() == null) {
+ // non-clustered, Keycloak started in dev mode?
+ return;
+ }
+ var cacheMode = cacheBuilder.clustering().cacheMode();
+ if (!cacheMode.isReplicated()) {
+ throw new RuntimeException("Unable to start Keycloak. '%s' cache must be replicated but is %s".formatted(WORK_CACHE_NAME, cacheMode.friendlyCacheModeString().toLowerCase()));
+ }
+ }
+
+ /**
+ * Removes clustered caches from the {@code holder}.
+ *
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches are configured.
+ */
+ public static void removeClusteredCaches(ConfigurationBuilderHolder holder) {
+ logger.debug("Removing clustered caches");
+ Arrays.stream(InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES).forEach(holder.getNamedConfigurationBuilders()::remove);
+ }
+
+ /**
+ * Configures the maximum number of entries for the specified caches, bounding them to this limit and preventing
+ * excessive memory usage.
+ *
+ * @param keycloakConfig The Keycloak configuration, which provides the maximum entry counts for the caches.
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches are configured.
+ * @param caches The {@link Stream} containing the names of the caches to configure with a maximum count.
+ * @throws IllegalStateException if an Infinispan cache from the provided {@code caches} stream is not defined in
+ * the {@code holder}. This could indicate a missing or incorrect configuration.
+ */
+ public static void configureCacheMaxCount(Config.Scope keycloakConfig, ConfigurationBuilderHolder holder, Stream caches) {
+ for (var it = caches.iterator(); it.hasNext(); ) {
+ var name = it.next();
+ var builder = holder.getNamedConfigurationBuilders().get(name);
+ if (builder == null) {
+ throw cacheNotFound(name);
+ }
+ setMemoryMaxCount(keycloakConfig, name, builder);
+ }
+ }
+
+ /**
+ * Configures all the sessions caches when persistent user sessions feature is enabled.
+ *
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches are configured.
+ * @throws IllegalStateException if an Infinispan cache from the provided {@code caches} stream is not defined in
+ * the {@code holder}. This could indicate a missing or incorrect configuration.
+ */
+ public static void configureSessionsCachesForPersistentSessions(ConfigurationBuilderHolder holder) {
+ logger.debug("Configuring session cache (persistent user sessions)");
+ for (var name : Arrays.asList(USER_SESSION_CACHE_NAME, CLIENT_SESSION_CACHE_NAME, OFFLINE_USER_SESSION_CACHE_NAME, OFFLINE_CLIENT_SESSION_CACHE_NAME)) {
+ var builder = holder.getNamedConfigurationBuilders().get(name);
+ if (builder == null) {
+ throw cacheNotFound(name);
+ }
+ if (builder.memory().maxCount() == -1) {
+ logger.infof("Persistent user sessions enabled and no memory limit found in configuration. Setting max entries for %s to 10000 entries.", name);
+ builder.memory().maxCount(10000);
+ }
+ /* The number of owners for these caches then need to be set to `1` to avoid backup owners with inconsistent data.
+ As primary owner evicts a key based on its locally evaluated maxCount setting, it wouldn't tell the backup owner about this, and then the backup owner would be left with a soon-to-be-outdated key.
+ While a `remove` is forwarded to the backup owner regardless if the key exists on the primary owner, a `computeIfPresent` is not, and it would leave a backup owner with an outdated key.
+ With the number of owners set to `1`, there will be no backup owners, so this is the setting to choose with persistent sessions enabled to ensure consistent data in the caches. */
+ builder.clustering().hash().numOwners(1);
+ }
+ }
+
+ /**
+ * Configures all the sessions caches when persistent user sessions feature is enabled.
+ *
+ * @param holder The {@link ConfigurationBuilderHolder} where the caches are configured.
+ * @throws IllegalStateException if an Infinispan cache from the provided {@code caches} stream is not defined in
+ * the {@code holder}. This could indicate a missing or incorrect configuration.
+ */
+ public static void configureSessionsCachesForVolatileSessions(ConfigurationBuilderHolder holder) {
+ logger.debug("Configuring session cache (volatile user sessions)");
+ for (var name : Arrays.asList(USER_SESSION_CACHE_NAME, CLIENT_SESSION_CACHE_NAME, OFFLINE_USER_SESSION_CACHE_NAME, OFFLINE_CLIENT_SESSION_CACHE_NAME)) {
+ var builder = holder.getNamedConfigurationBuilders().get(name);
+ if (builder == null) {
+ throw cacheNotFound(name);
+ }
+ if (builder.memory().maxCount() != -1) {
+ logger.warnf("Persistent user sessions disabled and memory limit found in configuration for cache %s. This might be a misconfiguration! Update your Infinispan configuration to remove this message.", name);
+ }
+ if (builder.memory().maxCount() == 10000 && (name.equals(USER_SESSION_CACHE_NAME) || name.equals(CLIENT_SESSION_CACHE_NAME))) {
+ logger.warnf("Persistent user sessions disabled and memory limit is set to default value 10000. Ignoring cache limits to avoid losing sessions for cache %s.", name);
+ builder.memory().maxCount(-1);
+ }
+ if (builder.clustering().hash().attributes().attribute(HashConfiguration.NUM_OWNERS).get() == 1 && builder.persistence().stores().isEmpty()) {
+ logger.warnf("Number of owners is one for cache %s, and no persistence is configured. This might be a misconfiguration as you will lose data when a single node is restarted!", name);
+ }
+ }
+ }
+
+ // private methods below
+
+ private static void configureRevisionCache(ConfigurationBuilderHolder holder, String baseCache, String revisionCache, long defaultMaxEntries) {
+ var baseBuilder = holder.getNamedConfigurationBuilders().get(baseCache);
+ if (baseBuilder == null) {
+ throw cacheNotFound(baseCache);
+ }
+ var maxCount = baseBuilder.memory().maxCount();
+ maxCount = maxCount > 0 ? 2 * maxCount : defaultMaxEntries;
+ logger.debugf("Creating revision cache '%s' with max-count %s", revisionCache, maxCount);
+ holder.getNamedConfigurationBuilders().put(revisionCache, getRevisionCacheConfig(maxCount));
+ }
+
+ private static void setMemoryMaxCount(Config.Scope keycloakConfig, String name, ConfigurationBuilder builder) {
+ var maxCount = keycloakConfig.getInt(maxCountConfigKey(name));
+ if (maxCount != null) {
+ logger.debugf("Overwriting max-count for cache '%s' to %s entries", name, maxCount);
+ builder.memory().maxCount(maxCount);
+ }
+ }
+
+ public static String maxCountConfigKey(String name) {
+ return name + MAX_COUNT_SUFFIX;
+ }
+
+ private static IllegalStateException cacheNotFound(String cache) {
+ return new IllegalStateException("Infinispan cache '%s' not found.".formatted(cache));
+ }
+
+ // cache configuration below
+
+ public static ConfigurationBuilder getCrlCacheConfig() {
+ var builder = createCacheConfigurationBuilder();
+ builder.memory().whenFull(EvictionStrategy.REMOVE).maxCount(InfinispanConnectionProvider.CRL_CACHE_DEFAULT_MAX);
+ return builder;
+ }
+
+ public static ConfigurationBuilder getRevisionCacheConfig(long maxEntries) {
+ var builder = createCacheConfigurationBuilder();
+ builder.simpleCache(false);
+ builder.invocationBatching().enable().transaction().transactionMode(TransactionMode.TRANSACTIONAL);
+
+ // Use Embedded manager even in managed ( wildfly/eap ) environment. We don't want infinispan to participate in global transaction
+ builder.transaction().transactionManagerLookup(new EmbeddedTransactionManagerLookup());
+
+ builder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+ if (builder.memory().storage().canStoreReferences()) {
+ builder.encoding().mediaType(MediaType.APPLICATION_OBJECT_TYPE);
+ }
+
+ builder.memory().whenFull(EvictionStrategy.REMOVE).maxCount(maxEntries);
+
+ return builder;
+ }
+
+ public static ConfigurationBuilder createCacheConfigurationBuilder() {
+ ConfigurationBuilder builder = new ConfigurationBuilder();
+
+ // need to force the encoding to application/x-java-object to avoid unnecessary conversion of keys/values. See WFLY-14356.
+ builder.encoding().mediaType(MediaType.APPLICATION_OBJECT_TYPE);
+
+ // needs to be disabled if transaction is enabled
+ builder.simpleCache(true);
+
+ return builder;
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/DefaultCacheEmbeddedConfigProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/DefaultCacheEmbeddedConfigProviderFactory.java
new file mode 100644
index 00000000000..c3609235555
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/DefaultCacheEmbeddedConfigProviderFactory.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.spi.infinispan.impl.embedded;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import io.micrometer.core.instrument.Metrics;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.StatisticsConfigurationBuilder;
+import org.infinispan.configuration.global.ShutdownHookBehavior;
+import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
+import org.infinispan.configuration.parsing.ParserRegistry;
+import org.infinispan.metrics.config.MicrometerMeterRegisterConfigurationBuilder;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.Profile;
+import org.keycloak.config.CachingOptions;
+import org.keycloak.config.MetricsOptions;
+import org.keycloak.infinispan.module.configuration.global.KeycloakConfigurationBuilder;
+import org.keycloak.infinispan.util.InfinispanUtils;
+import org.keycloak.marshalling.Marshalling;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+import org.keycloak.spi.infinispan.CacheEmbeddedConfigProvider;
+import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderFactory;
+import org.keycloak.spi.infinispan.JGroupsCertificateProvider;
+import org.keycloak.spi.infinispan.impl.Util;
+
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.ALL_CACHES_NAME;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_MAX_COUNT_CACHES;
+import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOCAL_CACHE_NAMES;
+
+/**
+ * The default implementation of {@link CacheEmbeddedConfigProviderFactory}.
+ *
+ * It builds a {@link ConfigurationBuilderHolder} based on the Keycloak configuration.
+ *
+ * Advanced users may extend this class and overwrite the method {@link #createConfiguration(KeycloakSessionFactory)}.
+ * They have access to the {@link ConfigurationBuilderHolder}, and they can modify it as needed for their custom
+ * providers.
+ */
+public class DefaultCacheEmbeddedConfigProviderFactory implements CacheEmbeddedConfigProviderFactory, CacheEmbeddedConfigProvider {
+
+ private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final String PROVIDER_ID = "default";
+
+ // Configuration
+ public static final String CONFIG = "configFile";
+ private static final String METRICS = "metricsEnabled";
+ private static final String HISTOGRAMS = "metricsHistogramsEnabled";
+ public static final String STACK = "stack";
+ public static final String NODE_NAME = "nodeName";
+ public static final String SITE_NAME = "siteName";
+
+ private volatile ConfigurationBuilderHolder builderHolder;
+ private volatile Config.Scope keycloakConfig;
+
+ @Override
+ public CacheEmbeddedConfigProvider create(KeycloakSession session) {
+ lazyInit(session.getKeycloakSessionFactory());
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ this.keycloakConfig = config;
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ lazyInit(factory);
+ }
+
+ @Override
+ public ConfigurationBuilderHolder configuration() {
+ return builderHolder;
+ }
+
+ @Override
+ public void close() {
+ //no-op
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public List getConfigMetadata() {
+ var builder = ProviderConfigurationBuilder.create();
+ Util.copyFromOption(builder, CONFIG, "file", ProviderConfigProperty.STRING_TYPE, CachingOptions.CACHE_CONFIG_FILE, false);
+ Util.copyFromOption(builder, HISTOGRAMS, "enabled", ProviderConfigProperty.BOOLEAN_TYPE, CachingOptions.CACHE_METRICS_HISTOGRAMS_ENABLED, false);
+ Util.copyFromOption(builder, METRICS, "enabled", ProviderConfigProperty.BOOLEAN_TYPE, MetricsOptions.INFINISPAN_METRICS_ENABLED, false);
+ Stream.concat(Arrays.stream(LOCAL_CACHE_NAMES), Arrays.stream(CLUSTERED_MAX_COUNT_CACHES))
+ .forEach(name -> Util.copyFromOption(builder, CacheConfigurator.maxCountConfigKey(name), "max-count", ProviderConfigProperty.INTEGER_TYPE, CachingOptions.maxCountOption(name), false));
+ createTopologyProperties(builder);
+ return builder.build();
+ }
+
+ @Override
+ public Set> dependsOn() {
+ return Set.of(JGroupsCertificateProvider.class);
+ }
+
+ private void lazyInit(KeycloakSessionFactory factory) {
+ if (builderHolder != null) {
+ return;
+ }
+ synchronized (this) {
+ if (builderHolder != null) {
+ return;
+ }
+ try {
+ builderHolder = createConfiguration(factory);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ protected ConfigurationBuilderHolder createConfiguration(KeycloakSessionFactory factory) throws IOException {
+ var holder = parseConfiguration(keycloakConfig, factory);
+ if (InfinispanUtils.isRemoteInfinispan()) {
+ return configureMultiSite(holder, keycloakConfig);
+ }
+ if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
+ return configureSingleSiteWithPersistentSessions(holder, keycloakConfig, factory);
+ }
+ return configureSingleSiteWithVolatileSessions(holder, keycloakConfig, factory);
+ }
+
+ private static ConfigurationBuilderHolder configureSingleSiteWithVolatileSessions(ConfigurationBuilderHolder holder, Config.Scope keycloakConfig, KeycloakSessionFactory factory) {
+ singleSiteConfiguration(keycloakConfig, holder, factory);
+ CacheConfigurator.configureSessionsCachesForVolatileSessions(holder);
+ return holder;
+ }
+
+ private static ConfigurationBuilderHolder configureSingleSiteWithPersistentSessions(ConfigurationBuilderHolder holder, Config.Scope keycloakConfig, KeycloakSessionFactory factory) {
+ singleSiteConfiguration(keycloakConfig, holder, factory);
+ CacheConfigurator.configureSessionsCachesForPersistentSessions(holder);
+ return holder;
+ }
+
+ private static ConfigurationBuilderHolder configureMultiSite(ConfigurationBuilderHolder holder, Config.Scope keycloakConfig) {
+ logger.debug("Configuring Infinispan for multi-site deployment");
+ CacheConfigurator.removeClusteredCaches(holder);
+ CacheConfigurator.checkCachesExist(holder, Arrays.stream(LOCAL_CACHE_NAMES));
+ configureMetrics(keycloakConfig, holder);
+ // Disable JGroups, not required when the data is stored in the Remote Cache.
+ // The existing caches are local and do not require JGroups to work properly.
+ holder.getGlobalConfigurationBuilder().nonClusteredDefault();
+ return holder;
+ }
+
+ private static ConfigurationBuilderHolder parseConfiguration(Config.Scope keycloakConfig, KeycloakSessionFactory factory) throws IOException {
+ var configFile = keycloakConfig.get(CONFIG);
+ if (configFile == null) {
+ throw new IllegalArgumentException("Option 'configFile' needs to be specified");
+ }
+ var configPath = Paths.get(configFile);
+ var path = configPath.toFile().exists() ?
+ configPath.toFile().getAbsolutePath() :
+ configPath.getFileName().toString();
+
+ logger.debugf("Parsing Infinispan configuration from file: %s", path);
+ var holder = new ParserRegistry(DefaultCacheEmbeddedConfigProviderFactory.class.getClassLoader())
+ .parseFile(path);
+ // We must disable the Infinispan default ShutdownHook as we manage the EmbeddedCacheManager lifecycle explicitly
+ // with #shutdown and multiple calls to EmbeddedCacheManager#stop can lead to Exceptions being thrown.
+ holder.getGlobalConfigurationBuilder().shutdown().hookBehavior(ShutdownHookBehavior.DONT_REGISTER);
+ Marshalling.configure(holder.getGlobalConfigurationBuilder());
+ holder.getGlobalConfigurationBuilder()
+ .addModule(KeycloakConfigurationBuilder.class)
+ .setKeycloakSessionFactory(factory);
+ CacheConfigurator.applyDefaultConfiguration(holder);
+ CacheConfigurator.configureLocalCaches(keycloakConfig, holder);
+ JGroupsConfigurator.configureTopology(keycloakConfig, holder);
+ return holder;
+ }
+
+ private static void singleSiteConfiguration(Config.Scope config, ConfigurationBuilderHolder holder, KeycloakSessionFactory factory) {
+ logger.debug("Configuring Infinispan for single-site deployment");
+ CacheConfigurator.checkCachesExist(holder, Arrays.stream(ALL_CACHES_NAME));
+ CacheConfigurator.configureCacheMaxCount(config, holder, Arrays.stream(CLUSTERED_MAX_COUNT_CACHES));
+ CacheConfigurator.validateWorkCacheConfiguration(holder);
+ KeycloakModelUtils.runJobInTransaction(factory, session -> JGroupsConfigurator.configureJGroups(config, holder, session));
+ configureMetrics(config, holder);
+ }
+
+ private static void configureMetrics(Config.Scope keycloakConfig, ConfigurationBuilderHolder holder) {
+ //metrics are disabled by default (check MetricsOptions class)
+ if (keycloakConfig.getBoolean(METRICS, Boolean.FALSE)) {
+ logger.debug("Enabling Infinispan metrics");
+ var builder = holder.getGlobalConfigurationBuilder();
+ builder.addModule(MicrometerMeterRegisterConfigurationBuilder.class)
+ .meterRegistry(Metrics.globalRegistry);
+ builder.cacheContainer().statistics(true);
+ builder.metrics()
+ .namesAsTags(true)
+ .histograms(keycloakConfig.getBoolean(HISTOGRAMS, Boolean.FALSE));
+ holder.getNamedConfigurationBuilders()
+ .values()
+ .stream()
+ .map(ConfigurationBuilder::statistics)
+ .forEach(StatisticsConfigurationBuilder::enable);
+ }
+ }
+
+ private static void createTopologyProperties(ProviderConfigurationBuilder builder) {
+ builder.property()
+ .name(NODE_NAME)
+ .helpText("Sets the name of the current node. This is a friendly name to make logs, etc. make more sense.")
+ .label("name")
+ .type(ProviderConfigProperty.STRING_TYPE)
+ .add();
+ builder.property()
+ .name(SITE_NAME)
+ .helpText("The name of the site where this node runs. Used for server hinting.")
+ .label("name")
+ .type(ProviderConfigProperty.STRING_TYPE)
+ .add();
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/JGroupsConfigurator.java b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/JGroupsConfigurator.java
new file mode 100644
index 00000000000..e9a302fc2d1
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/spi/infinispan/impl/embedded/JGroupsConfigurator.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2025 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.spi.infinispan.impl.embedded;
+
+import java.lang.invoke.MethodHandles;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.infinispan.commons.configuration.attributes.Attribute;
+import org.infinispan.configuration.global.TransportConfigurationBuilder;
+import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
+import org.infinispan.remoting.transport.jgroups.EmbeddedJGroupsChannelConfigurator;
+import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
+import org.jboss.logging.Logger;
+import org.jgroups.conf.ClassConfigurator;
+import org.jgroups.conf.ProtocolConfiguration;
+import org.jgroups.protocols.TCP_NIO2;
+import org.jgroups.protocols.UDP;
+import org.jgroups.stack.Protocol;
+import org.jgroups.util.DefaultSocketFactory;
+import org.jgroups.util.SocketFactory;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
+import org.keycloak.connections.jpa.util.JpaUtils;
+import org.keycloak.jgroups.protocol.KEYCLOAK_JDBC_PING2;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.spi.infinispan.JGroupsCertificateProvider;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.TrustManager;
+
+import static org.infinispan.configuration.global.TransportConfiguration.STACK;
+
+/**
+ * Utility class to configure JGroups based on the Keycloak configuration.
+ */
+public final class JGroupsConfigurator {
+
+ private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
+ private static final String TLS_PROTOCOL_VERSION = "TLSv1.3";
+ private static final String TLS_PROTOCOL = "TLS";
+
+ private JGroupsConfigurator() {
+ }
+
+ static {
+ // Use custom Keycloak JDBC_PING implementation that workarounds issue https://issues.redhat.com/browse/JGRP-2870
+ // The id 1025 follows this instruction: https://github.com/belaban/JGroups/blob/38219e9ec1c629fa2f7929e3b53d1417d8e60b61/conf/jg-protocol-ids.xml#L85
+ ClassConfigurator.addProtocol((short) 1025, KEYCLOAK_JDBC_PING2.class);
+ }
+
+ /**
+ * Configures JGroups based on the Keycloak configuration.
+ *
+ * @param config The Keycloak configuration.
+ * @param holder The {@link ConfigurationBuilderHolder} where the transport is configured.
+ * @param session The {@link KeycloakSession} sessions for Database access.
+ */
+ public static void configureJGroups(Config.Scope config, ConfigurationBuilderHolder holder, KeycloakSession session) {
+ var stack = config.get(DefaultCacheEmbeddedConfigProviderFactory.STACK);
+ if (stack != null) {
+ transportOf(holder).stack(stack);
+ }
+ configureDiscovery(holder, session);
+ configureTls(holder, session);
+ warnDeprecatedStack(holder);
+ }
+
+ /**
+ * Configures the topology information in the Infinispan transport.
+ *
+ * @param config The Keycloak configuration.
+ * @param holder The {@link ConfigurationBuilderHolder} where the transport is configured.
+ */
+ public static void configureTopology(Config.Scope config, ConfigurationBuilderHolder holder) {
+ if (System.getProperty(InfinispanConnectionProvider.JBOSS_SITE_NAME) != null) {
+ throw new IllegalArgumentException(
+ String.format("System property %s is in use. Use --spi-cache-embedded-%s-site-name config option instead",
+ InfinispanConnectionProvider.JBOSS_SITE_NAME, DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID));
+ }
+ if (System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME) != null) {
+ throw new IllegalArgumentException(
+ String.format("System property %s is in use. Use --spi-cache-embedded-%s-node-name config option instead",
+ InfinispanConnectionProvider.JBOSS_NODE_NAME, DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID));
+ }
+ var transport = transportOf(holder);
+ var nodeName = config.get(DefaultCacheEmbeddedConfigProviderFactory.NODE_NAME);
+ if (nodeName != null) {
+ transport.nodeName(nodeName);
+ }
+ //legacy option, for backwards compatibility --spi-connections-infinispan-quarkus-site-name
+ var legacySiteName = Config.scope(InfinispanConnectionSpi.SPI_NAME, "quarkus").get("site-name");
+ if (legacySiteName != null) {
+ logger.warn("--spi-connections-infinispan-quarkus-site-name is deprecated and may be removed in the future. Use --spi-cache-embedded-%s-site-name".formatted(DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID));
+ }
+ var siteName = config.get(DefaultCacheEmbeddedConfigProviderFactory.SITE_NAME, legacySiteName);
+ if (siteName != null) {
+ transport.siteId(siteName);
+ }
+ }
+
+ private static void configureTls(ConfigurationBuilderHolder holder, KeycloakSession session) {
+ var provider = session.getProvider(JGroupsCertificateProvider.class);
+ if (provider == null || !provider.isEnabled()) {
+ return;
+ }
+ var factory = createSocketFactory(provider);
+ transportOf(holder).addProperty(JGroupsTransport.SOCKET_FACTORY, factory);
+ validateTlsAvailable(holder);
+ logger.info("JGroups Encryption enabled (mTLS).");
+ }
+
+ private static SocketFactory createSocketFactory(JGroupsCertificateProvider provider) {
+ try {
+ var sslContext = SSLContext.getInstance(TLS_PROTOCOL);
+ sslContext.init(new KeyManager[]{provider.keyManager()}, new TrustManager[]{provider.trustManager()}, null);
+ return createFromContext(sslContext);
+ } catch (KeyManagementException | NoSuchAlgorithmException e) {
+ // we should have valid certificates and keys.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static SocketFactory createFromContext(SSLContext context) {
+ DefaultSocketFactory socketFactory = new DefaultSocketFactory(context);
+ final SSLParameters serverParameters = new SSLParameters();
+ serverParameters.setProtocols(new String[]{TLS_PROTOCOL_VERSION});
+ serverParameters.setNeedClientAuth(true);
+ socketFactory.setServerSocketConfigurator(socket -> ((SSLServerSocket) socket).setSSLParameters(serverParameters));
+ return socketFactory;
+ }
+
+ private static void configureDiscovery(ConfigurationBuilderHolder holder, KeycloakSession session) {
+ var stackXmlAttribute = transportStackOf(holder);
+ if (stackXmlAttribute.isModified() && !isJdbcPingStack(stackXmlAttribute.get())) {
+ logger.debugf("Custom stack configured (%s). JDBC_PING discovery disabled.", stackXmlAttribute.get());
+ return;
+ }
+
+ logger.debug("JDBC_PING discovery enabled.");
+ if (!stackXmlAttribute.isModified()) {
+ // defaults to jdbc-ping
+ transportOf(holder).stack("jdbc-ping");
+ }
+
+ var em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+ var stackName = transportStackOf(holder).get();
+ var isUdp = stackName.endsWith("udp");
+ var tableName = JpaUtils.getTableNameForNativeQuery("JGROUPS_PING", em);
+ var stack = getProtocolConfigurations(tableName, isUdp ? "PING" : "MPING");
+ var connectionFactory = (JpaConnectionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
+ holder.addJGroupsStack(new JpaFactoryAwareJGroupsChannelConfigurator(stackName, stack, connectionFactory, isUdp), null);
+
+ transportOf(holder).stack(stackName);
+ JGroupsConfigurator.logger.info("JGroups JDBC_PING discovery enabled.");
+ }
+
+ private static List getProtocolConfigurations(String tableName, String discoveryProtocol) {
+ var attributes = Map.of(
+ // Leave initialize_sql blank as table is already created by Keycloak
+ "initialize_sql", "",
+ // Explicitly specify clear and select_all SQL to ensure "cluster_name" column is used, as the default
+ // "cluster" cannot be used with Oracle DB as it's a reserved word.
+ "clear_sql", String.format("DELETE from %s WHERE cluster_name=?", tableName),
+ "delete_single_sql", String.format("DELETE from %s WHERE address=?", tableName),
+ "insert_single_sql", String.format("INSERT INTO %s values (?, ?, ?, ?, ?)", tableName),
+ "select_all_pingdata_sql", String.format("SELECT address, name, ip, coord FROM %s WHERE cluster_name=?", tableName),
+ "remove_all_data_on_view_change", "true",
+ "register_shutdown_hook", "false",
+ "stack.combine", "REPLACE",
+ "stack.position", discoveryProtocol
+ );
+ return List.of(new ProtocolConfiguration(KEYCLOAK_JDBC_PING2.class.getName(), attributes));
+ }
+
+ private static void warnDeprecatedStack(ConfigurationBuilderHolder holder) {
+ var stackName = transportStackOf(holder).get();
+ switch (stackName) {
+ case "jdbc-ping-udp":
+ case "tcp":
+ case "udp":
+ case "azure":
+ case "ec2":
+ case "google":
+ logger.warnf("Stack '%s' is deprecated. We recommend to use 'jdbc-ping' instead", stackName);
+ }
+ }
+
+ private static TransportConfigurationBuilder transportOf(ConfigurationBuilderHolder holder) {
+ return holder.getGlobalConfigurationBuilder().transport();
+ }
+
+ private static Attribute transportStackOf(ConfigurationBuilderHolder holder) {
+ var transport = transportOf(holder);
+ assert transport != null;
+ return transport.attributes().attribute(STACK);
+ }
+
+ private static void validateTlsAvailable(ConfigurationBuilderHolder holder) {
+ var stackName = transportStackOf(holder).get();
+ if (stackName == null) {
+ // unable to validate
+ return;
+ }
+ var config = transportOf(holder).build();
+ for (var protocol : config.transport().jgroups().configurator(stackName).getProtocolStack()) {
+ var name = protocol.getProtocolName();
+ if (name.equals(UDP.class.getSimpleName()) ||
+ name.equals(UDP.class.getName()) ||
+ name.equals(TCP_NIO2.class.getSimpleName()) ||
+ name.equals(TCP_NIO2.class.getName())) {
+ throw new RuntimeException("Cache TLS is not available with protocol " + name);
+ }
+ }
+ }
+
+ private static boolean isJdbcPingStack(String stackName) {
+ return "jdbc-ping".equals(stackName) || "jdbc-ping-udp".equals(stackName);
+ }
+
+ private static class JpaFactoryAwareJGroupsChannelConfigurator extends EmbeddedJGroupsChannelConfigurator {
+
+ private final JpaConnectionProviderFactory factory;
+
+ public JpaFactoryAwareJGroupsChannelConfigurator(String name, List stack, JpaConnectionProviderFactory factory, boolean isUdp) {
+ super(name, stack, null, isUdp ? "udp" : "tcp");
+ this.factory = Objects.requireNonNull(factory);
+ }
+
+ @Override
+ public void afterCreation(Protocol protocol) {
+ super.afterCreation(protocol);
+ if (protocol instanceof KEYCLOAK_JDBC_PING2 kcPing) {
+ kcPing.setJpaConnectionProviderFactory(factory);
+ }
+ }
+ }
+}
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 8297262fbf9..8b67e435e46 100644
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -18,3 +18,5 @@
org.keycloak.connections.infinispan.InfinispanConnectionSpi
org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi
org.keycloak.spi.infinispan.JGroupsCertificateProviderSpi
+org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi
+org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi
\ No newline at end of file
diff --git a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderFactory
similarity index 81%
rename from quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider
rename to model/infinispan/src/main/resources/META-INF/services/org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderFactory
index e0488026ff0..56395b39e75 100644
--- a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderFactory
@@ -1,5 +1,5 @@
#
-# Copyright 2021 Red Hat, Inc. and/or its affiliates
+# Copyright 2025 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,4 +15,4 @@
# limitations under the License.
#
-org.keycloak.quarkus.runtime.storage.infinispan.QuarkusCacheManagerProvider
\ No newline at end of file
+org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProviderFactory
\ No newline at end of file
diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/MetricsOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/MetricsOptions.java
index 8586534dfb6..72f74854028 100644
--- a/quarkus/config-api/src/main/java/org/keycloak/config/MetricsOptions.java
+++ b/quarkus/config-api/src/main/java/org/keycloak/config/MetricsOptions.java
@@ -15,4 +15,11 @@ public class MetricsOptions {
.defaultValue(Boolean.TRUE)
.hidden() // This is intended to be enabled all the time when global metrics are enabled, therefore this option is hidden
.build();
+
+ public static final Option INFINISPAN_METRICS_ENABLED = new OptionBuilder<>("infinispan-metrics-enabled", Boolean.class)
+ .category(OptionCategory.METRICS)
+ .description("If Infinispan metrics should be collected and exposed.")
+ .defaultValue(Boolean.FALSE)
+ .hidden() // Intentional, Infinispan metrics are enabled when '--metrics-enabled' are enabled.
+ .build();
}
diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CacheBuildSteps.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CacheBuildSteps.java
deleted file mode 100644
index 3f82bcb6fe5..00000000000
--- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CacheBuildSteps.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.quarkus.deployment;
-
-import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
-import io.quarkus.deployment.annotations.BuildProducer;
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.annotations.Consume;
-import io.quarkus.deployment.annotations.ExecutionTime;
-import io.quarkus.deployment.annotations.Record;
-import io.quarkus.deployment.logging.LoggingSetupBuildItem;
-import jakarta.enterprise.context.ApplicationScoped;
-import org.keycloak.quarkus.runtime.KeycloakRecorder;
-import org.keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory;
-
-public class CacheBuildSteps {
-
- @Consume(ProfileBuildItem.class)
- @Consume(ConfigBuildItem.class)
- // Consume LoggingSetupBuildItem.class and record RUNTIME_INIT are necessary to ensure that logging is set up before the caches are initialized.
- // This is to prevent the class TP in JGroups to pick up the trace logging at start up. While the logs will not appear on the console,
- // they will still be created and use CPU cycles and create garbage collection.
- // See: https://issues.redhat.com/browse/JGRP-2130 for the JGroups discussion, and https://github.com/keycloak/keycloak/issues/29129 for the issue Keycloak had with this.
- @Consume(LoggingSetupBuildItem.class)
- @Record(ExecutionTime.RUNTIME_INIT)
- @BuildStep
- void configureInfinispan(KeycloakRecorder recorder, BuildProducer syntheticBeanBuildItems) {
- syntheticBeanBuildItems.produce(SyntheticBeanBuildItem.configure(CacheManagerFactory.class)
- .scope(ApplicationScoped.class)
- .unremovable()
- .setRuntimeInit()
- .runtimeValue(recorder.createCacheInitializer()).done());
- }
-}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java
index 0d22e3b89f8..aafc9676f1c 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java
@@ -17,21 +17,25 @@
package org.keycloak.quarkus.runtime;
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
import io.agroal.api.AgroalDataSource;
import io.quarkus.agroal.DataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
-import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import liquibase.Scope;
import liquibase.servicelocator.ServiceLocator;
import org.hibernate.cfg.AvailableSettings;
-import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.protostream.SerializationContextInitializer;
-import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.crypto.CryptoIntegration;
@@ -46,33 +50,14 @@ import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.storage.database.liquibase.FastServiceLocator;
-import org.keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.theme.ClasspathThemeProviderFactory;
import org.keycloak.truststore.TruststoreBuilder;
import org.keycloak.userprofile.DeclarativeUserProfileProviderFactory;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.annotation.Annotation;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.BiConsumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.keycloak.quarkus.runtime.configuration.Configuration.getKcConfigValue;
-
@Recorder
public class KeycloakRecorder {
- private static final Logger logger = Logger.getLogger(KeycloakRecorder.class);
-
public void initConfig() {
Config.init(new MicroProfileConfigProvider());
}
@@ -123,77 +108,29 @@ public class KeycloakRecorder {
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes));
}
- public RuntimeValue createCacheInitializer() {
- try {
- CacheManagerFactory cacheManagerFactory = new CacheManagerFactory(getInfinispanConfigFile());
- return new RuntimeValue<>(cacheManagerFactory);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private String getInfinispanConfigFile() {
- String configFile = getKcConfigValue("spi-connections-infinispan-quarkus-config-file").getValue();
-
- if (configFile == null) {
- throw new IllegalArgumentException("Option 'configFile' needs to be specified");
- }
-
- Path configPath = Paths.get(configFile);
- String path;
-
- if (configPath.toFile().exists()) {
- path = configPath.toFile().getAbsolutePath();
- } else {
- path = configPath.getFileName().toString();
- }
-
- logger.debugf("Infinispan configuration file: %s", path);
-
- InputStream url = FileLookupFactory.newInstance().lookupFile(path, KeycloakRecorder.class.getClassLoader());
-
- if (url == null) {
- throw new IllegalArgumentException("Could not load cluster configuration file at [" + configPath + "]");
- }
-
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(url))) {
- return reader.lines().collect(Collectors.joining("\n"));
- } catch (Exception cause) {
- throw new RuntimeException("Failed to read clustering configuration from [" + url + "]", cause);
- }
- }
-
public void setDefaultUserProfileConfiguration(UPConfig configuration) {
DeclarativeUserProfileProviderFactory.setDefaultConfig(configuration);
}
public HibernateOrmIntegrationRuntimeInitListener createUserDefinedUnitListener(String name) {
- return new HibernateOrmIntegrationRuntimeInitListener() {
- @Override
- public void contributeRuntimeProperties(BiConsumer propertyCollector) {
- try (InstanceHandle instance = Arc.container().instance(
- AgroalDataSource.class, new DataSource() {
- @Override public Class extends Annotation> annotationType() {
- return DataSource.class;
- }
+ return propertyCollector -> {
+ try (InstanceHandle instance = Arc.container().instance(
+ AgroalDataSource.class, new DataSource() {
+ @Override public Class extends Annotation> annotationType() {
+ return DataSource.class;
+ }
- @Override public String value() {
- return name;
- }
- })) {
- propertyCollector.accept(AvailableSettings.DATASOURCE, instance.get());
- }
+ @Override public String value() {
+ return name;
+ }
+ })) {
+ propertyCollector.accept(AvailableSettings.DATASOURCE, instance.get());
}
};
}
public HibernateOrmIntegrationRuntimeInitListener createDefaultUnitListener() {
- return new HibernateOrmIntegrationRuntimeInitListener() {
- @Override
- public void contributeRuntimeProperties(BiConsumer propertyCollector) {
- propertyCollector.accept(AvailableSettings.DEFAULT_SCHEMA, Configuration.getRawValue("kc.db-schema"));
- }
- };
+ return propertyCollector -> propertyCollector.accept(AvailableSettings.DEFAULT_SCHEMA, Configuration.getRawValue("kc.db-schema"));
}
public void setCryptoProvider(FipsMode fipsMode) {
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java
index 085ff2645fe..4935a7d083d 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/CachingPropertyMappers.java
@@ -8,6 +8,7 @@ import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;
+import com.google.common.base.CaseFormat;
import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.common.Profile;
import org.keycloak.config.CachingOptions;
@@ -39,7 +40,7 @@ final class CachingPropertyMappers {
.build(),
fromOption(CachingOptions.CACHE_STACK)
.isEnabled(CachingPropertyMappers::cacheSetToInfinispan, CACHE_STACK_SET_TO_ISPN)
- .to("kc.spi-connections-infinispan-quarkus-stack")
+ .to("kc.spi-cache-embedded-default-stack")
.paramLabel("stack")
.build(),
fromOption(CachingOptions.CACHE_CONFIG_FILE)
@@ -51,7 +52,7 @@ final class CachingPropertyMappers {
} else
return null;
})
- .to("kc.spi-connections-infinispan-quarkus-config-file")
+ .to("kc.spi-cache-embedded-default-config-file")
.transformer(CachingPropertyMappers::resolveConfigFile)
.validator(s -> {
if (!Files.exists(Paths.get(resolveConfigFile(s, null)))) {
@@ -126,6 +127,7 @@ final class CachingPropertyMappers {
.build(),
fromOption(CachingOptions.CACHE_METRICS_HISTOGRAMS_ENABLED)
.isEnabled(MetricsPropertyMappers::metricsEnabled, MetricsPropertyMappers.METRICS_ENABLED_MSG)
+ .to("kc.spi-cache-embedded-default-metrics-histograms-enabled")
.build()
);
@@ -210,6 +212,7 @@ final class CachingPropertyMappers {
return fromOption(CachingOptions.maxCountOption(cacheName))
.isEnabled(isEnabled, enabledWhen)
.paramLabel("max-count")
+ .to("kc.spi-cache-embedded-default-%s-max-count".formatted(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, cacheName)))
.build();
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/MetricsPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/MetricsPropertyMappers.java
index bb7763462d8..4176cba9bdd 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/MetricsPropertyMappers.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/MetricsPropertyMappers.java
@@ -20,6 +20,11 @@ final class MetricsPropertyMappers {
fromOption(MetricsOptions.PASSWORD_VALIDATION_COUNTER_ENABLED)
.to("kc.spi-credential-keycloak-password-metrics-enabled")
.isEnabled(MetricsPropertyMappers::metricsEnabled, "metrics are enabled")
+ .build(),
+ fromOption(MetricsOptions.INFINISPAN_METRICS_ENABLED)
+ .mapFrom(MetricsOptions.METRICS_ENABLED)
+ .to("kc.spi-cache-embedded-default-metrics-enabled")
+ .isEnabled(MetricsPropertyMappers::metricsEnabled, "metrics are enabled")
.build()
};
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java
deleted file mode 100644
index ff4825633f4..00000000000
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.quarkus.runtime.storage.infinispan;
-
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-import java.util.stream.Stream;
-
-import io.micrometer.core.instrument.Metrics;
-import org.infinispan.configuration.cache.ConfigurationBuilder;
-import org.infinispan.configuration.cache.HashConfiguration;
-import org.infinispan.configuration.global.ShutdownHookBehavior;
-import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
-import org.infinispan.configuration.parsing.ParserRegistry;
-import org.infinispan.manager.DefaultCacheManager;
-import org.infinispan.manager.EmbeddedCacheManager;
-import org.infinispan.metrics.config.MicrometerMeterRegisterConfigurationBuilder;
-import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
-import org.jboss.logging.Logger;
-import org.keycloak.common.Profile;
-import org.keycloak.common.util.MultiSiteUtils;
-import org.keycloak.config.CachingOptions;
-import org.keycloak.config.MetricsOptions;
-import org.keycloak.config.Option;
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.connections.infinispan.InfinispanUtil;
-import org.keycloak.infinispan.util.InfinispanUtils;
-import org.keycloak.jgroups.JGroupsConfigurator;
-import org.keycloak.marshalling.Marshalling;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.quarkus.runtime.configuration.Configuration;
-
-import javax.net.ssl.SSLContext;
-
-import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PASSWORD_PROPERTY;
-import static org.keycloak.config.CachingOptions.CACHE_REMOTE_USERNAME_PROPERTY;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_NAME;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOCAL_CACHE_NAMES;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_AND_CLIENT_SESSION_CACHES;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
-import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.WORK_CACHE_NAME;
-
-public class CacheManagerFactory {
-
- public static final Logger logger = Logger.getLogger(CacheManagerFactory.class);
- // Map with the default cache configuration if the cache is not present in the XML.
- private static final Map> DEFAULT_CONFIGS = Map.of(
- CRL_CACHE_NAME, InfinispanUtil::getCrlCacheConfig
- );
- private static final Supplier TO_NULL = () -> null;
-
- private volatile EmbeddedCacheManager cacheManager;
- private final JGroupsConfigurator jGroupsConfigurator;
-
- public CacheManagerFactory(String config) {
- ConfigurationBuilderHolder builder = new ParserRegistry().parse(config);
- jGroupsConfigurator = JGroupsConfigurator.create(builder);
- }
-
- public EmbeddedCacheManager getOrCreateEmbeddedCacheManager(KeycloakSession keycloakSession) {
- if (cacheManager != null)
- return cacheManager;
-
- synchronized (this) {
- if (cacheManager == null) {
- cacheManager = startEmbeddedCacheManager(keycloakSession);
- }
- }
- return cacheManager;
- }
-
- private EmbeddedCacheManager startEmbeddedCacheManager(KeycloakSession session) {
- logger.info("Starting Infinispan embedded cache manager");
- var builder = jGroupsConfigurator.holder();
-
- // We must disable the Infinispan default ShutdownHook as we manage the EmbeddedCacheManager lifecycle explicitly
- // with #shutdown and multiple calls to EmbeddedCacheManager#stop can lead to Exceptions being thrown
- builder.getGlobalConfigurationBuilder().shutdown().hookBehavior(ShutdownHookBehavior.DONT_REGISTER);
-
- Marshalling.configure(builder.getGlobalConfigurationBuilder());
- assertAllCachesAreConfigured(builder,
- // skip revision caches, those are defined by DefaultInfinispanConnectionProviderFactory
- Arrays.stream(LOCAL_CACHE_NAMES)
- .filter(Predicate.not(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME::equals))
- .filter(Predicate.not(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME::equals))
- .filter(Predicate.not(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME::equals))
- );
- if (InfinispanUtils.isRemoteInfinispan()) {
- var builders = builder.getNamedConfigurationBuilders();
- // remove all distributed caches
- logger.debug("Removing all distributed caches.");
- for (String cacheName : CLUSTERED_CACHE_NAMES) {
- if (hasRemoteStore(builders.get(cacheName))) {
- logger.warnf("remote-store configuration detected for cache '%s'. Explicit cache configuration ignored when using '%s' or '%s' Features.", cacheName, Profile.Feature.CLUSTERLESS.getKey(), Profile.Feature.MULTI_SITE.getKey());
- }
- builders.remove(cacheName);
- }
- // Disable JGroups, not required when the data is stored in the Remote Cache.
- // The existing caches are local and do not require JGroups to work properly.
- builder.getGlobalConfigurationBuilder().nonClusteredDefault();
- } else {
- // embedded mode!
- assertAllCachesAreConfigured(builder, Arrays.stream(CLUSTERED_CACHE_NAMES));
- if (builder.getNamedConfigurationBuilders().entrySet().stream().anyMatch(c -> c.getValue().clustering().cacheMode().isClustered())) {
- if (jGroupsConfigurator.isLocal()) {
- throw new RuntimeException("Unable to use clustered cache with local mode.");
- }
- }
- jGroupsConfigurator.configure(session);
- configureCacheMaxCount(builder, CachingOptions.CLUSTERED_MAX_COUNT_CACHES);
- configureSessionsCaches(builder);
- validateWorkCacheConfiguration(builder);
- }
- configureCacheMaxCount(builder, CachingOptions.LOCAL_MAX_COUNT_CACHES);
- checkForRemoteStores(builder);
- configureMetrics(builder);
-
- return new DefaultCacheManager(builder, isStartEagerly());
- }
-
- private static void configureMetrics(ConfigurationBuilderHolder holder) {
- if (Configuration.isTrue(MetricsOptions.METRICS_ENABLED)) {
- holder.getGlobalConfigurationBuilder().addModule(MicrometerMeterRegisterConfigurationBuilder.class);
- holder.getGlobalConfigurationBuilder().module(MicrometerMeterRegisterConfigurationBuilder.class).meterRegistry(Metrics.globalRegistry);
- holder.getGlobalConfigurationBuilder().cacheContainer().statistics(true);
- holder.getGlobalConfigurationBuilder().metrics().namesAsTags(true);
- if (Configuration.isTrue(CachingOptions.CACHE_METRICS_HISTOGRAMS_ENABLED)) {
- holder.getGlobalConfigurationBuilder().metrics().histograms(true);
- }
- holder.getNamedConfigurationBuilders().forEach((s, configurationBuilder) -> configurationBuilder.statistics().enabled(true));
- }
- }
-
- private static boolean isRemoteTLSEnabled() {
- return Configuration.isTrue(CachingOptions.CACHE_REMOTE_TLS_ENABLED);
- }
-
- private static boolean isRemoteAuthenticationEnabled() {
- return Configuration.getOptionalKcValue(CACHE_REMOTE_USERNAME_PROPERTY).isPresent() ||
- Configuration.getOptionalKcValue(CACHE_REMOTE_PASSWORD_PROPERTY).isPresent();
- }
-
- private static SSLContext createSSLContext() {
- try {
- // uses the default Java Runtime TrustStore, or the one generated by Keycloak (see org.keycloak.truststore.TruststoreBuilder)
- var sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, null, null);
- return sslContext;
- } catch (NoSuchAlgorithmException | KeyManagementException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static boolean isStartEagerly() {
- // eagerly starts caches by default
- return Boolean.parseBoolean(System.getProperty("kc.cache-ispn-start-eagerly", Boolean.TRUE.toString()));
- }
-
- private static int getStartTimeout() {
- return Integer.getInteger("kc.cache-ispn-start-timeout", 120);
- }
-
- /**
- *
- * RemoteStores were previously used when running Keycloak in the CrossDC environment, and Keycloak code
- * contained a lot of performance optimizations to make this work smoothly.
- * These optimizations are now removed as recommended multi-site setup no longer relies on RemoteStores.
- * A lot of blueprints in the wild may turn into very ineffective setups.
- *
- * For this reason, we need to be more opinionated on what configurations we allow,
- * especially for user and client sessions.
- * This method is responsible for checking the Infinispan configuration used and either change the configuration to
- * more effective when possible or refuse to start with recommendations for users to change their config.
- *
- * @param builder Cache configuration builder
- */
- private static void checkForRemoteStores(ConfigurationBuilderHolder builder) {
- for (String cacheName : USER_AND_CLIENT_SESSION_CACHES) {
- ConfigurationBuilder cacheConfigurationBuilder = builder.getNamedConfigurationBuilders().get(cacheName);
-
- if (cacheConfigurationBuilder != null && hasRemoteStore(cacheConfigurationBuilder)) {
- if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
- logger.warnf("Feature %s is enabled and remote store detected for cache '%s'. Remote stores are no longer needed when sessions stored in the database. The configuration will be ignored.", Profile.Feature.PERSISTENT_USER_SESSIONS.getKey(), cacheName);
- cacheConfigurationBuilder.persistence().stores().removeIf(RemoteStoreConfigurationBuilder.class::isInstance);
- } else {
- logger.fatalf("Remote stores are not supported for embedded caches storing user and client sessions.%nFor keeping user sessions across restarts, use feature %s which is enabled by default.%nFor multi-site support, enable %s.",
- Profile.Feature.PERSISTENT_USER_SESSIONS.getKey(), Profile.Feature.MULTI_SITE.getKey());
-
- throw new RuntimeException("Remote stores for storing user and client sessions are not supported.");
- }
- }
- }
- }
-
- private static void configureSessionsCaches(ConfigurationBuilderHolder builder) {
- Stream.of(USER_SESSION_CACHE_NAME, CLIENT_SESSION_CACHE_NAME, OFFLINE_USER_SESSION_CACHE_NAME, OFFLINE_CLIENT_SESSION_CACHE_NAME)
- .forEach(cacheName -> {
- var configurationBuilder = builder.getNamedConfigurationBuilders().get(cacheName);
- if (MultiSiteUtils.isPersistentSessionsEnabled()) {
- if (configurationBuilder.memory().maxCount() == -1) {
- logger.infof("Persistent user sessions enabled and no memory limit found in configuration. Setting max entries for %s to 10000 entries.", cacheName);
- configurationBuilder.memory().maxCount(10000);
- }
- /* The number of owners for these caches then need to be set to `1` to avoid backup owners with inconsistent data.
- As primary owner evicts a key based on its locally evaluated maxCount setting, it wouldn't tell the backup owner about this, and then the backup owner would be left with a soon-to-be-outdated key.
- While a `remove` is forwarded to the backup owner regardless if the key exists on the primary owner, a `computeIfPresent` is not, and it would leave a backup owner with an outdated key.
- With the number of owners set to `1`, there will be no backup owners, so this is the setting to choose with persistent sessions enabled to ensure consistent data in the caches. */
- configurationBuilder.clustering().hash().numOwners(1);
- } else {
- if (configurationBuilder.memory().maxCount() != -1) {
- logger.warnf("Persistent user sessions disabled and memory limit found in configuration for cache %s. This might be a misconfiguration! Update your Infinispan configuration to remove this message.", cacheName);
- }
- if (configurationBuilder.memory().maxCount() == 10000 && (cacheName.equals(USER_SESSION_CACHE_NAME) || cacheName.equals(CLIENT_SESSION_CACHE_NAME))) {
- logger.warnf("Persistent user sessions disabled and memory limit is set to default value 10000. Ignoring cache limits to avoid losing sessions for cache %s.", cacheName);
- configurationBuilder.memory().maxCount(-1);
- }
- if (configurationBuilder.clustering().hash().attributes().attribute(HashConfiguration.NUM_OWNERS).get() == 1
- && configurationBuilder.persistence().stores().isEmpty()) {
- logger.warnf("Number of owners is one for cache %s, and no persistence is configured. This might be a misconfiguration as you will lose data when a single node is restarted!", cacheName);
- }
- }
- });
- }
-
- private static void configureCacheMaxCount(ConfigurationBuilderHolder holder, String[] caches) {
- for (String cache : caches) {
- var memory = holder.getNamedConfigurationBuilders().get(cache).memory();
- String propKey = CachingOptions.cacheMaxCountProperty(cache);
- Configuration.getOptionalKcValue(propKey)
- .map(Integer::parseInt)
- .ifPresent(memory::maxCount);
- }
- }
-
- private static void assertAllCachesAreConfigured(ConfigurationBuilderHolder holder, Stream caches) {
- for (var it = caches.iterator() ; it.hasNext() ; ) {
- var cache = it.next();
- var builder = holder.getNamedConfigurationBuilders().get(cache);
- if (builder != null) {
- continue;
- }
- builder = DEFAULT_CONFIGS.getOrDefault(cache, TO_NULL).get();
- if (builder == null) {
- throw new IllegalStateException("Infinispan cache '%s' not found. Make sure it is defined in your XML configuration file.".formatted(cache));
- }
- holder.getNamedConfigurationBuilders().put(cache, builder);
- }
- }
-
- private static void validateWorkCacheConfiguration(ConfigurationBuilderHolder builder) {
- var cacheBuilder = builder.getNamedConfigurationBuilders().get(WORK_CACHE_NAME);
- if (cacheBuilder == null) {
- throw new RuntimeException("Unable to start Keycloak. '%s' cache is missing".formatted(WORK_CACHE_NAME));
- }
- if (builder.getGlobalConfigurationBuilder().cacheContainer().transport().getTransport() == null) {
- // non-clustered, Keycloak started in dev mode?
- return;
- }
- var cacheMode = cacheBuilder.clustering().cacheMode();
- if (!cacheMode.isReplicated()) {
- throw new RuntimeException("Unable to start Keycloak. '%s' cache must be replicated but is %s".formatted(WORK_CACHE_NAME, cacheMode.friendlyCacheModeString().toLowerCase()));
- }
- }
-
- public static String requiredStringProperty(String propertyName) {
- return Configuration.getOptionalKcValue(propertyName).orElseThrow(() -> new RuntimeException("Property " + propertyName + " required but not specified"));
- }
-
- public static int requiredIntegerProperty(Option option) {
- return Configuration.getOptionalIntegerValue(option)
- .orElseThrow(() -> new RuntimeException("Property '%s' required but not specified".formatted(option.getKey())));
- }
-
- private static boolean hasRemoteStore(ConfigurationBuilder builder) {
- return builder.persistence().stores().stream().anyMatch(RemoteStoreConfigurationBuilder.class::isInstance);
- }
-}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java
deleted file mode 100644
index 096080853e4..00000000000
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.quarkus.runtime.storage.infinispan;
-
-import io.quarkus.arc.Arc;
-import org.keycloak.Config;
-import org.keycloak.cluster.ManagedCacheManagerProvider;
-import org.keycloak.models.KeycloakSession;
-
-/**
- * @author Pedro Igor
- */
-@SuppressWarnings({"unchecked", "resource"})
-public final class QuarkusCacheManagerProvider implements ManagedCacheManagerProvider {
-
- @Override
- public C getEmbeddedCacheManager(KeycloakSession keycloakSession, Config.Scope config) {
- return (C) Arc.container().instance(CacheManagerFactory.class).get().getOrCreateEmbeddedCacheManager(keycloakSession);
- }
-}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusInfinispanConnectionFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusInfinispanConnectionFactory.java
deleted file mode 100644
index 0168a4b9366..00000000000
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusInfinispanConnectionFactory.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.quarkus.runtime.storage.infinispan;
-
-import org.infinispan.manager.EmbeddedCacheManager;
-import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
-import org.keycloak.provider.ProviderConfigProperty;
-import org.keycloak.provider.ProviderConfigurationBuilder;
-
-import java.util.List;
-
-/**
- * @author Pedro Igor
- */
-public class QuarkusInfinispanConnectionFactory extends DefaultInfinispanConnectionProviderFactory {
-
- @Override
- protected EmbeddedCacheManager initContainerManaged(EmbeddedCacheManager cacheManager) {
- EmbeddedCacheManager result = super.initContainerManaged(cacheManager);
- // force closing the cache manager when stopping the provider
- // we probably want to refactor the default impl a bit to support this use case
- containerManaged = false;
- return result;
- }
-
- @Override
- public int order() {
- return 100;
- }
-
- @Override
- public String getId() {
- return "quarkus";
- }
-
- @Override
- public List getConfigMetadata() {
- return ProviderConfigurationBuilder.create()
- .property()
- .name("site-name")
- .helpText("Site name for multi-site deployments")
- .type("string")
- .add()
- .build();
- }
-}
diff --git a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
deleted file mode 100644
index fec4d5c4bdd..00000000000
--- a/quarkus/runtime/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# /*
-# * Copyright 2021 Red Hat, Inc. and/or its affiliates
-# * and other contributors as indicated by the @author tags.
-# *
-# * Licensed under the Apache License, Version 2.0 (the "License");
-# * you may not use this file except in compliance with the License.
-# * You may obtain a copy of the License at
-# *
-# * http://www.apache.org/licenses/LICENSE-2.0
-# *
-# * Unless required by applicable law or agreed to in writing, software
-# * distributed under the License is distributed on an "AS IS" BASIS,
-# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# * See the License for the specific language governing permissions and
-# * limitations under the License.
-# */
-#
-
-org.keycloak.quarkus.runtime.storage.infinispan.QuarkusInfinispanConnectionFactory
\ No newline at end of file
diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java
index 076b58400c3..db9278916ef 100644
--- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java
+++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/AbstractConfigurationTest.java
@@ -137,7 +137,7 @@ public abstract class AbstractConfigurationTest {
Environment.removeHomeDir();
}
- protected Config.Scope initConfig(String... scope) {
+ protected static Config.Scope initConfig(String... scope) {
Config.init(new MicroProfileConfigProvider(createConfig()));
return Config.scope(scope);
}
diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java
index a28b01d4923..e04af6e45b8 100644
--- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java
+++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigurationTest.java
@@ -50,6 +50,8 @@ import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.vault.FilesKeystoreVaultProviderFactory;
import org.keycloak.quarkus.runtime.vault.FilesPlainTextVaultProviderFactory;
+import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi;
+import org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProviderFactory;
import org.mariadb.jdbc.MariaDbDataSource;
import org.postgresql.xa.PGXADataSource;
@@ -375,21 +377,21 @@ public class ConfigurationTest extends AbstractConfigurationTest {
public void testClusterConfig() {
// Cluster enabled by default, but disabled for the "dev" profile
String conf = Environment.getHomeDir() + File.separator + "conf" + File.separator;
- Assert.assertEquals(conf + "cache-ispn.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
+ Assert.assertEquals(conf + "cache-ispn.xml", cacheEmbeddedConfiguration().get(DefaultCacheEmbeddedConfigProviderFactory.CONFIG));
// If explicitly set, then it is always used regardless of the profile
System.clearProperty(org.keycloak.common.util.Environment.PROFILE);
ConfigArgsConfigSource.setCliArgs("--cache-config-file=cluster-foo.xml");
- Assert.assertEquals(conf + "cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
+ Assert.assertEquals(conf + "cluster-foo.xml", cacheEmbeddedConfiguration().get(DefaultCacheEmbeddedConfigProviderFactory.CONFIG));
System.setProperty(org.keycloak.common.util.Environment.PROFILE, "dev");
- Assert.assertEquals(conf + "cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
+ Assert.assertEquals(conf + "cluster-foo.xml", cacheEmbeddedConfiguration().get(DefaultCacheEmbeddedConfigProviderFactory.CONFIG));
ConfigArgsConfigSource.setCliArgs("");
- Assert.assertEquals("cache-local.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
+ Assert.assertEquals("cache-local.xml", cacheEmbeddedConfiguration().get(DefaultCacheEmbeddedConfigProviderFactory.CONFIG));
ConfigArgsConfigSource.setCliArgs("--cache-stack=foo");
- Assert.assertEquals("foo", initConfig("connectionsInfinispan", "quarkus").get("stack"));
+ Assert.assertEquals("foo", cacheEmbeddedConfiguration().get(DefaultCacheEmbeddedConfigProviderFactory.STACK));
}
@Test
@@ -584,4 +586,8 @@ public class ConfigurationTest extends AbstractConfigurationTest {
SmallRyeConfig config = createConfig();
assertEquals("200k", config.getConfigValue("quarkus.http.limits.max-header-size").getValue());
}
+
+ private static Config.Scope cacheEmbeddedConfiguration() {
+ return initConfig(CacheEmbeddedConfigProviderSpi.SPI_NAME, DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID);
+ }
}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/ExternalInfinispanTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/ExternalInfinispanTest.java
index 0a03e9335f7..e9ddf079f52 100644
--- a/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/ExternalInfinispanTest.java
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/storage/database/ExternalInfinispanTest.java
@@ -44,7 +44,7 @@ public class ExternalInfinispanTest {
"--cache-remote-username=keycloak",
"--cache-remote-password=Password1!",
"--cache-remote-tls-enabled=false",
- "--spi-connections-infinispan-quarkus-site-name=ISPN",
+ "--spi-cache-embedded-default-site-name=ISPN",
"--spi-load-balancer-check-remote-poll-interval=500",
"-Dkc.cache-remote-create-caches=true",
"--verbose"
@@ -62,7 +62,7 @@ public class ExternalInfinispanTest {
"--cache-remote-username=keycloak",
"--cache-remote-password=Password1!",
"--cache-remote-tls-enabled=false",
- "--spi-connections-infinispan-quarkus-site-name=ISPN",
+ "--spi-cache-embedded-default-site-name=ISPN",
"--spi-load-balancer-check-remote-poll-interval=500",
"-Dkc.cache-remote-create-caches=true",
"--verbose"
@@ -93,6 +93,17 @@ public class ExternalInfinispanTest {
"--verbose"
})
void testSiteNameAsSystemProperty(CLIResult cliResult) {
- cliResult.assertMessage("System property jboss.site.name is in use. Use --spi-connections-infinispan-quarkus-site-name config option instead");
+ cliResult.assertMessage("System property jboss.site.name is in use. Use --spi-cache-embedded-default-site-name config option instead");
+ }
+
+ @Test
+ @Launch({
+ "start-dev",
+ "--cache=ispn",
+ "-Djboss.node.name=ISPN",
+ "--verbose"
+ })
+ void testNodeNameAsSystemProperty(CLIResult cliResult) {
+ cliResult.assertMessage("System property jboss.node.name is in use. Use --spi-cache-embedded-default-node-name config option instead");
}
}
diff --git a/server-spi-private/src/main/java/org/keycloak/cluster/ManagedCacheManagerProvider.java b/server-spi-private/src/main/java/org/keycloak/cluster/ManagedCacheManagerProvider.java
deleted file mode 100644
index 43da64901c6..00000000000
--- a/server-spi-private/src/main/java/org/keycloak/cluster/ManagedCacheManagerProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2019 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.cluster;
-
-import org.keycloak.Config;
-import org.keycloak.models.KeycloakSession;
-
-/**
- * A Service Provider Interface (SPI) that allows to plug-in an embedded or remote cache manager instance.
- *
- * @author Pedro Igor
- */
-public interface ManagedCacheManagerProvider {
-
- C getEmbeddedCacheManager(KeycloakSession keycloakSession, Config.Scope config);
-
- /**
- * @return A RemoteCacheManager if the features {@link org.keycloak.common.Profile.Feature#CLUSTERLESS} or {@link org.keycloak.common.Profile.Feature#MULTI_SITE} is enabled, {@code null} otherwise.
- * @deprecated The RemoteCacheManager is created and managed by keycloak. Use InfinispanConnectionProvider to retrieve it and implement CacheRemoteConfigProvider to overwrite the configuration.
- */
- @Deprecated(since = "26.3", forRemoval = true)
- default C getRemoteCacheManager(Config.Scope config) {
- return null;
- }
-}
diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md
index a8a3c982268..1c0876c5f58 100644
--- a/testsuite/integration-arquillian/HOW-TO-RUN.md
+++ b/testsuite/integration-arquillian/HOW-TO-RUN.md
@@ -394,7 +394,6 @@ Then run any cluster test as usual.
mvn -f testsuite/integration-arquillian/tests/base/pom.xml \
-Pauth-server-cluster-undertow,db-mysql \
-Dsession.cache.owners=2 \
- -Dkeycloak.connectionsInfinispan.sessionsOwners=2 \
-Dbackends.console.output=true \
-Dauth.server.log.check=false \
-Dfrontend.console.output=true \
@@ -410,8 +409,8 @@ You can use any cluster test (eg. AuthenticationSessionFailoverClusterTest) and
-Dauth.server.undertow=false -Dauth.server.undertow.cluster=true -Dauth.server.cluster=true
-Dkeycloak.connectionsJpa.url=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver
- -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsInfinispan.clustered=true -Dresources
- -Dkeycloak.connectionsInfinispan.sessionsOwners=2 -Dsession.cache.owners=2
+ -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dresources
+ -Dsession.cache.owners=2
Invalidation tests (subclass of `AbstractInvalidationClusterTest`) don't need last two properties.
@@ -423,8 +422,8 @@ This mode is useful for develop/manual tests of clustering features. You will ne
1) Run KeycloakServer server1 with:
-Dkeycloak.connectionsJpa.url=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver
- -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsInfinispan.clustered=true
- -Dkeycloak.connectionsInfinispan.sessionsOwners=2 -Dresources
+ -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak
+ -Dresources
and argument: `-p 8181`
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java
index 2db9d41817c..9e74d8f036f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java
@@ -44,7 +44,6 @@ import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
@@ -134,7 +133,7 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
}
@Override
- public void undeploy(Descriptor descriptor) throws DeploymentException {
+ public void undeploy(Descriptor descriptor) {
}
@@ -183,7 +182,7 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
}
if (configuration.getRoute() != null) {
- commands.add("-Djboss.node.name=" + configuration.getRoute());
+ commands.add("--spi-cache-embedded-default-node-name=" + configuration.getRoute());
}
if (System.getProperty("auth.server.quarkus.log-level") != null) {
@@ -231,7 +230,7 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
commands.add("--cache-remote-username=keycloak");
commands.add("--cache-remote-password=Password1!");
commands.add("--cache-remote-tls-enabled=false");
- commands.add("--spi-connections-infinispan-quarkus-site-name=test");
+ commands.add("--spi-cache-embedded-default-site-name=test");
configuration.appendJavaOpts("-Dkc.cache-remote-create-caches=true");
System.setProperty("kc.cache-remote-create-caches", "true");
}
@@ -359,8 +358,8 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
}
connection.disconnect();
- } catch (Exception ignore) {
- e = ignore;
+ } catch (Exception exception) {
+ e = exception;
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index aee5e9a7141..91eac8bdc98 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -173,17 +173,9 @@
}
},
- "connectionsInfinispan": {
+ "cacheEmbedded": {
"default": {
- "jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}",
- "jgroupsBindAddr": "${keycloak.connectionsInfinispan.jgroupsBindAddr:127.0.0.1}",
- "nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:}",
- "siteName": "${keycloak.connectionsInfinispan.siteName:}",
- "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
- "async": "${keycloak.connectionsInfinispan.async:false}",
- "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:1}",
- "l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}",
- "embedded": "${keycloak.connectionsInfinispan.embedded:true}"
+ "nodeName": "${keycloak.cacheEmbedded.nodeName,jboss.node.name:}"
}
},
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index f1b45033fa7..7ab273874fe 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -245,9 +245,7 @@
node1
${undertow.remote}
{
- "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.8",
- "keycloak.connectionsInfinispan.nodeName": "node1",
- "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}"
+ "keycloak.cacheEmbedded.nodeName": "node1"
}
@@ -264,9 +262,7 @@
node2
${undertow.remote}
{
- "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.8",
- "keycloak.connectionsInfinispan.nodeName": "node2",
- "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}"
+ "keycloak.cacheEmbedded.nodeName": "node2"
}
@@ -327,9 +323,7 @@
ha
5005
{
- "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.8",
- "keycloak.connectionsInfinispan.nodeName": "node1",
- "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}"
+ "keycloak.cacheEmbedded.nodeName": "node1"
}
-Xms512m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=512m -Djava.net.preferIPv4Stack=true
@@ -351,9 +345,7 @@
ha
5006
{
- "keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.8",
- "keycloak.connectionsInfinispan.nodeName": "node2",
- "keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}"
+ "keycloak.cacheEmbedded.nodeName": "node2"
}
-Xms512m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=512m -Djava.net.preferIPv4Stack=true
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 360095162c6..1a688ce74b0 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -585,7 +585,6 @@
${keycloak.cacheRemote.port}
${keycloak.cacheRemote.port.2}
${keycloak.cacheRemote.hostname}
- ${keycloak.connectionsInfinispan.sessionsOwners}
${keycloak.testsuite.logging.pattern}
${keycloak.connectionsJpa.url.crossdc}
@@ -718,7 +717,6 @@
false
true
true
- 2
@@ -1328,8 +1326,6 @@
true
true
-
- 2
diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java
index 4bec57507a8..83bcefd469c 100644
--- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java
+++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java
@@ -1,13 +1,13 @@
/*
* Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,6 +16,9 @@
*/
package org.keycloak.testsuite.model.parameters;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
import com.google.common.collect.ImmutableSet;
import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
@@ -34,6 +37,7 @@ import org.keycloak.models.cache.authorization.CachedStoreFactorySpi;
import org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory;
import org.keycloak.models.cache.infinispan.InfinispanUserCacheProviderFactory;
import org.keycloak.models.cache.infinispan.authorization.InfinispanCacheStoreFactoryProviderFactory;
+import org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProviderFactory;
import org.keycloak.models.session.UserSessionPersisterSpi;
import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory;
@@ -44,16 +48,18 @@ import org.keycloak.provider.Spi;
import org.keycloak.sessions.AuthenticationSessionSpi;
import org.keycloak.sessions.StickySessionEncoderProviderFactory;
import org.keycloak.sessions.StickySessionEncoderSpi;
+import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderFactory;
+import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi;
+import org.keycloak.spi.infinispan.JGroupsCertificateProviderFactory;
+import org.keycloak.spi.infinispan.JGroupsCertificateProviderSpi;
+import org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProviderFactory;
+import org.keycloak.storage.configuration.ServerConfigStorageProviderFactory;
+import org.keycloak.storage.configuration.ServerConfigurationStorageProviderSpi;
import org.keycloak.testsuite.model.Config;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.timer.TimerProviderFactory;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProviderFactory;
-
/**
- *
* @author hmlnarik
*/
public class Infinispan extends KeycloakModelParameters {
@@ -61,44 +67,46 @@ public class Infinispan extends KeycloakModelParameters {
private static final AtomicInteger NODE_COUNTER = new AtomicInteger();
static final Set> ALLOWED_SPIS = ImmutableSet.>builder()
- .add(AuthenticationSessionSpi.class)
- .add(CacheRealmProviderSpi.class)
- .add(CachedStoreFactorySpi.class)
- .add(CacheUserProviderSpi.class)
- .add(InfinispanConnectionSpi.class)
- .add(StickySessionEncoderSpi.class)
- .add(UserSessionPersisterSpi.class)
- .add(SingleUseObjectSpi.class)
- .add(PublicKeyStorageSpi.class)
- .add(CachePublicKeyProviderSpi.class)
-
- .build();
+ .add(AuthenticationSessionSpi.class)
+ .add(CacheRealmProviderSpi.class)
+ .add(CachedStoreFactorySpi.class)
+ .add(CacheUserProviderSpi.class)
+ .add(InfinispanConnectionSpi.class)
+ .add(StickySessionEncoderSpi.class)
+ .add(UserSessionPersisterSpi.class)
+ .add(SingleUseObjectSpi.class)
+ .add(PublicKeyStorageSpi.class)
+ .add(CachePublicKeyProviderSpi.class)
+ .add(CacheEmbeddedConfigProviderSpi.class)
+ .add(JGroupsCertificateProviderSpi.class)
+ .add(ServerConfigurationStorageProviderSpi.class)
+ .build();
static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder()
- .add(InfinispanAuthenticationSessionProviderFactory.class)
- .add(InfinispanCacheRealmProviderFactory.class)
- .add(InfinispanCacheStoreFactoryProviderFactory.class)
- .add(InfinispanClusterProviderFactory.class)
- .add(InfinispanConnectionProviderFactory.class)
- .add(InfinispanUserCacheProviderFactory.class)
- .add(InfinispanUserSessionProviderFactory.class)
- .add(InfinispanUserLoginFailureProviderFactory.class)
- .add(InfinispanSingleUseObjectProviderFactory.class)
- .add(StickySessionEncoderProviderFactory.class)
- .add(TimerProviderFactory.class)
- .add(InfinispanPublicKeyStorageProviderFactory.class)
- .add(InfinispanCachePublicKeyProviderFactory.class)
- .add(InfinispanOrganizationProviderFactory.class)
- .build();
+ .add(InfinispanAuthenticationSessionProviderFactory.class)
+ .add(InfinispanCacheRealmProviderFactory.class)
+ .add(InfinispanCacheStoreFactoryProviderFactory.class)
+ .add(InfinispanClusterProviderFactory.class)
+ .add(InfinispanConnectionProviderFactory.class)
+ .add(InfinispanUserCacheProviderFactory.class)
+ .add(InfinispanUserSessionProviderFactory.class)
+ .add(InfinispanUserLoginFailureProviderFactory.class)
+ .add(InfinispanSingleUseObjectProviderFactory.class)
+ .add(StickySessionEncoderProviderFactory.class)
+ .add(TimerProviderFactory.class)
+ .add(InfinispanPublicKeyStorageProviderFactory.class)
+ .add(InfinispanCachePublicKeyProviderFactory.class)
+ .add(InfinispanOrganizationProviderFactory.class)
+ .add(CacheEmbeddedConfigProviderFactory.class)
+ .add(JGroupsCertificateProviderFactory.class)
+ .add(ServerConfigStorageProviderFactory.class)
+ .build();
@Override
public void updateConfig(Config cf) {
cf.spi("connectionsInfinispan")
.provider("default")
- .config("embedded", "true")
- .config("clustered", "true")
.config("useKeycloakTimeService", "true")
- .config("nodeName", "node-" + NODE_COUNTER.incrementAndGet())
.spi(UserLoginFailureSpi.NAME)
.provider(InfinispanUtils.EMBEDDED_PROVIDER_ID)
.config("stalledTimeoutInSeconds", "10")
@@ -106,8 +114,12 @@ public class Infinispan extends KeycloakModelParameters {
.provider(InfinispanUtils.EMBEDDED_PROVIDER_ID)
.config("sessionPreloadStalledTimeoutInSeconds", "10")
.config("offlineSessionCacheEntryLifespanOverride", "43200")
- .config("offlineClientSessionCacheEntryLifespanOverride", "43200")
- ;
+ .config("offlineClientSessionCacheEntryLifespanOverride", "43200");
+ cf.spi(CacheEmbeddedConfigProviderSpi.SPI_NAME)
+ .provider(DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID)
+ .config(DefaultCacheEmbeddedConfigProviderFactory.CONFIG, "test-ispn.xml")
+ .config(DefaultCacheEmbeddedConfigProviderFactory.NODE_NAME, "node-" + NODE_COUNTER.incrementAndGet());
+
}
public Infinispan() {
diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/RemoteInfinispan.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/RemoteInfinispan.java
index 6f4ca12c0ac..376563ff9eb 100644
--- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/RemoteInfinispan.java
+++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/RemoteInfinispan.java
@@ -31,7 +31,9 @@ import org.keycloak.models.sessions.infinispan.remote.RemoteStickySessionEncoder
import org.keycloak.models.sessions.infinispan.remote.RemoteUserLoginFailureProviderFactory;
import org.keycloak.models.sessions.infinispan.remote.RemoteUserSessionProviderFactory;
import org.keycloak.provider.ProviderFactory;
+import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi;
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi;
+import org.keycloak.spi.infinispan.impl.embedded.DefaultCacheEmbeddedConfigProviderFactory;
import org.keycloak.spi.infinispan.impl.remote.DefaultCacheRemoteConfigProviderFactory;
import org.keycloak.testsuite.model.Config;
import org.keycloak.testsuite.model.HotRodServerRule;
@@ -70,16 +72,16 @@ public class RemoteInfinispan extends KeycloakModelParameters {
var siteName = siteName(nodeCounter);
cf.spi("connectionsInfinispan")
.provider("default")
- .config("embedded", "true")
- .config("clustered", "true")
- .config("useKeycloakTimeService", "true")
- .config("nodeName", "node-" + nodeCounter)
- .config("siteName", siteName)
- .config("jgroupsUdpMcastAddr", mcastAddr(nodeCounter));
+ .config("useKeycloakTimeService", "true");
cf.spi(CacheRemoteConfigProviderSpi.SPI_NAME)
.provider(DefaultCacheRemoteConfigProviderFactory.PROVIDER_ID)
.config(DefaultCacheRemoteConfigProviderFactory.HOSTNAME, "localhost")
.config(DefaultCacheRemoteConfigProviderFactory.PORT, siteName.equals("site-2") ? "11333" : "11222");
+ cf.spi(CacheEmbeddedConfigProviderSpi.SPI_NAME)
+ .provider(DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID)
+ .config(DefaultCacheEmbeddedConfigProviderFactory.CONFIG, "test-ispn.xml")
+ .config(DefaultCacheEmbeddedConfigProviderFactory.SITE_NAME, siteName(NODE_COUNTER.get()))
+ .config(DefaultCacheEmbeddedConfigProviderFactory.NODE_NAME, "node-" + NODE_COUNTER.incrementAndGet());
}
}
diff --git a/testsuite/model/src/test/resources/test-ispn.xml b/testsuite/model/src/test/resources/test-ispn.xml
new file mode 100644
index 00000000000..b305b4cd68e
--- /dev/null
+++ b/testsuite/model/src/test/resources/test-ispn.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
index d68d68b55e7..b3fe0ca7c5a 100755
--- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
+++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
@@ -123,16 +123,9 @@
}
},
- "connectionsInfinispan": {
+ "cacheEmbedded": {
"default": {
- "jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}",
- "nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:}",
- "siteName": "${keycloak.connectionsInfinispan.siteName:}",
- "clustered": "${keycloak.connectionsInfinispan.clustered:}",
- "async": "${keycloak.connectionsInfinispan.async:}",
- "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:}",
- "l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:}",
- "embedded": "${keycloak.connectionsInfinispan.embedded:true}"
+ "nodeName": "${keycloak.cacheEmbedded.nodeName,jboss.node.name:}"
}
},