Make persistent sessions co-exist with remote cache feature (#30859)
Closes #30855 Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
523653ba2f
commit
d70f78072e
9 changed files with 140 additions and 63 deletions
23
.github/workflows/ci.yml
vendored
23
.github/workflows/ci.yml
vendored
|
@ -321,6 +321,10 @@ jobs:
|
||||||
if: needs.conditional.outputs.ci-store == 'true'
|
if: needs.conditional.outputs.ci-store == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 150
|
timeout-minutes: 150
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
variant: [ "pus-ec", "pus-rc" ]
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
@ -328,11 +332,24 @@ jobs:
|
||||||
name: Integration test setup
|
name: Integration test setup
|
||||||
uses: ./.github/actions/integration-test-setup
|
uses: ./.github/actions/integration-test-setup
|
||||||
|
|
||||||
- name: Run base tests without cache
|
- name: Run base tests
|
||||||
run: |
|
run: |
|
||||||
TESTS=`testsuite/integration-arquillian/tests/base/testsuites/suite.sh persistent-sessions`
|
TESTS=`testsuite/integration-arquillian/tests/base/testsuites/suite.sh persistent-sessions`
|
||||||
echo "Tests: $TESTS"
|
echo "Tests: $TESTS"
|
||||||
./mvnw test ${{ env.SUREFIRE_RETRY }} -Pauth-server-quarkus -Dauth.server.feature="persistent-user-sessions" -Dtest=$TESTS -pl testsuite/integration-arquillian/tests/base 2>&1 | misc/log/trimmer.sh
|
case "${{ matrix.variant }}" in
|
||||||
|
pus-ec)
|
||||||
|
VARIANT="-Dauth.server.feature=persistent-user-sessions"
|
||||||
|
;;
|
||||||
|
pus-rc)
|
||||||
|
VARIANT="-Pinfinispan-server -Dauth.server.feature=persistent-user-sessions,multi-site,remote-cache"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown Matrix element"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "Variant: $VARIANT"
|
||||||
|
./mvnw test ${{ env.SUREFIRE_RETRY }} -Pauth-server-quarkus $VARIANT -Dtest=$TESTS -pl testsuite/integration-arquillian/tests/base 2>&1 | misc/log/trimmer.sh
|
||||||
|
|
||||||
- name: Upload JVM Heapdumps
|
- name: Upload JVM Heapdumps
|
||||||
if: always()
|
if: always()
|
||||||
|
@ -349,7 +366,7 @@ jobs:
|
||||||
if: always()
|
if: always()
|
||||||
uses: ./.github/actions/archive-surefire-reports
|
uses: ./.github/actions/archive-surefire-reports
|
||||||
with:
|
with:
|
||||||
job-id: store-integration-tests-${{ matrix.db }}
|
job-id: store-integration-tests-${{ matrix.variant }}
|
||||||
|
|
||||||
- name: EC2 Maven Logs
|
- name: EC2 Maven Logs
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.affinity.KeyGenerator;
|
||||||
import org.infinispan.client.hotrod.RemoteCache;
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.infinispan.persistence.remote.RemoteStore;
|
import org.infinispan.persistence.remote.RemoteStore;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -76,7 +77,9 @@ import org.keycloak.provider.ProviderEvent;
|
||||||
import org.keycloak.provider.ProviderEventListener;
|
import org.keycloak.provider.ProviderEventListener;
|
||||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||||
|
|
||||||
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory, ServerInfoAwareProviderFactory, EnvironmentDependentProviderFactory {
|
import static org.keycloak.common.Profile.Feature.PERSISTENT_USER_SESSIONS;
|
||||||
|
|
||||||
|
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory<UserSessionProvider>, ServerInfoAwareProviderFactory, EnvironmentDependentProviderFactory {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
||||||
|
|
||||||
|
@ -87,6 +90,10 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
public static final String CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE = "offlineClientSessionCacheEntryLifespanOverride";
|
public static final String CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE = "offlineClientSessionCacheEntryLifespanOverride";
|
||||||
public static final String CONFIG_MAX_BATCH_SIZE = "maxBatchSize";
|
public static final String CONFIG_MAX_BATCH_SIZE = "maxBatchSize";
|
||||||
public static final int DEFAULT_MAX_BATCH_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 2);
|
public static final int DEFAULT_MAX_BATCH_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 2);
|
||||||
|
public static final String CONFIG_USE_CACHES = "useCaches";
|
||||||
|
private static final boolean DEFAULT_USE_CACHES = true;
|
||||||
|
public static final String CONFIG_USE_BATCHES = "useBatches";
|
||||||
|
private static final boolean DEFAULT_USE_BATCHES = true;
|
||||||
|
|
||||||
private long offlineSessionCacheEntryLifespanOverride;
|
private long offlineSessionCacheEntryLifespanOverride;
|
||||||
|
|
||||||
|
@ -103,17 +110,26 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
SerializeExecutionsByKey<String> serializerOfflineSession = new SerializeExecutionsByKey<>();
|
SerializeExecutionsByKey<String> serializerOfflineSession = new SerializeExecutionsByKey<>();
|
||||||
SerializeExecutionsByKey<UUID> serializerClientSession = new SerializeExecutionsByKey<>();
|
SerializeExecutionsByKey<UUID> serializerClientSession = new SerializeExecutionsByKey<>();
|
||||||
SerializeExecutionsByKey<UUID> serializerOfflineClientSession = new SerializeExecutionsByKey<>();
|
SerializeExecutionsByKey<UUID> serializerOfflineClientSession = new SerializeExecutionsByKey<>();
|
||||||
ArrayBlockingQueue<PersistentUpdate> asyncQueuePersistentUpdate = new ArrayBlockingQueue<>(1000);
|
ArrayBlockingQueue<PersistentUpdate> asyncQueuePersistentUpdate;
|
||||||
private PersistentSessionsWorker persistentSessionsWorker;
|
private PersistentSessionsWorker persistentSessionsWorker;
|
||||||
private int maxBatchSize;
|
private int maxBatchSize;
|
||||||
|
private boolean useCaches;
|
||||||
|
private boolean useBatches;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionProvider create(KeycloakSession session) {
|
public UserSessionProvider create(KeycloakSession session) {
|
||||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = null;
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = connections.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = null;
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = null;
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = connections.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionsCache = null;
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
|
||||||
|
if (useCaches) {
|
||||||
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
cache = connections.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
|
||||||
|
offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
|
||||||
|
clientSessionCache = connections.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
|
||||||
|
offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
|
||||||
return new PersistentUserSessionProvider(
|
return new PersistentUserSessionProvider(
|
||||||
|
@ -159,6 +175,11 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
offlineSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
|
offlineSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
|
||||||
offlineClientSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
|
offlineClientSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
|
||||||
maxBatchSize = config.getInt(CONFIG_MAX_BATCH_SIZE, DEFAULT_MAX_BATCH_SIZE);
|
maxBatchSize = config.getInt(CONFIG_MAX_BATCH_SIZE, DEFAULT_MAX_BATCH_SIZE);
|
||||||
|
useCaches = config.getBoolean(CONFIG_USE_CACHES, DEFAULT_USE_CACHES) && InfinispanUtils.isEmbeddedInfinispan();
|
||||||
|
useBatches = config.getBoolean(CONFIG_USE_BATCHES, DEFAULT_USE_BATCHES) && Profile.isFeatureEnabled(PERSISTENT_USER_SESSIONS);
|
||||||
|
if (useBatches) {
|
||||||
|
asyncQueuePersistentUpdate = new ArrayBlockingQueue<>(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,7 +189,14 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(ProviderEvent event) {
|
public void onEvent(ProviderEvent event) {
|
||||||
if (event instanceof PostMigrationEvent) {
|
if (event instanceof PostMigrationEvent) {
|
||||||
|
if (!useCaches) {
|
||||||
|
keyGenerator = new InfinispanKeyGenerator() {
|
||||||
|
@Override
|
||||||
|
protected <K> K generateKey(KeycloakSession session, Cache<K, ?> cache, KeyGenerator<K> keyGenerator) {
|
||||||
|
return keyGenerator.getKey();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
int preloadTransactionTimeout = getTimeoutForPreloadingSessionsSeconds();
|
int preloadTransactionTimeout = getTimeoutForPreloadingSessionsSeconds();
|
||||||
log.debugf("Will preload sessions with transaction timeout %d seconds", preloadTransactionTimeout);
|
log.debugf("Will preload sessions with transaction timeout %d seconds", preloadTransactionTimeout);
|
||||||
|
|
||||||
|
@ -182,6 +210,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
registerClusterListeners(session);
|
registerClusterListeners(session);
|
||||||
loadSessionsFromRemoteCaches(session);
|
loadSessionsFromRemoteCaches(session);
|
||||||
}, preloadTransactionTimeout);
|
}, preloadTransactionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (event instanceof UserModel.UserRemovedEvent) {
|
} else if (event instanceof UserModel.UserRemovedEvent) {
|
||||||
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
|
||||||
|
@ -208,7 +237,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) && useBatches) {
|
||||||
persistentSessionsWorker = new PersistentSessionsWorker(factory,
|
persistentSessionsWorker = new PersistentSessionsWorker(factory,
|
||||||
asyncQueuePersistentUpdate,
|
asyncQueuePersistentUpdate,
|
||||||
maxBatchSize);
|
maxBatchSize);
|
||||||
|
@ -428,7 +457,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported(Config.Scope config) {
|
public boolean isSupported(Config.Scope config) {
|
||||||
return InfinispanUtils.isEmbeddedInfinispan();
|
return InfinispanUtils.isEmbeddedInfinispan() || Profile.isFeatureEnabled(PERSISTENT_USER_SESSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -437,6 +466,8 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
info.put(CONFIG_OFFLINE_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, Long.toString(offlineSessionCacheEntryLifespanOverride));
|
info.put(CONFIG_OFFLINE_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, Long.toString(offlineSessionCacheEntryLifespanOverride));
|
||||||
info.put(CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, Long.toString(offlineClientSessionCacheEntryLifespanOverride));
|
info.put(CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, Long.toString(offlineClientSessionCacheEntryLifespanOverride));
|
||||||
info.put(CONFIG_MAX_BATCH_SIZE, Integer.toString(maxBatchSize));
|
info.put(CONFIG_MAX_BATCH_SIZE, Integer.toString(maxBatchSize));
|
||||||
|
info.put(CONFIG_USE_CACHES, Boolean.toString(useCaches));
|
||||||
|
info.put(CONFIG_USE_BATCHES, Boolean.toString(useBatches));
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,6 +494,12 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
.helpText("Override how long offline user sessions should be kept in memory")
|
.helpText("Override how long offline user sessions should be kept in memory")
|
||||||
.add();
|
.add();
|
||||||
|
|
||||||
|
builder.property()
|
||||||
|
.name(CONFIG_USE_CACHES)
|
||||||
|
.type("boolean")
|
||||||
|
.helpText("Enable or disable caches. Enabled by default unless the external feature to use only external remote caches is used")
|
||||||
|
.add();
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -425,6 +425,10 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
|
|
||||||
// Try lookup userSession from remoteCache
|
// Try lookup userSession from remoteCache
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
||||||
|
if (cache == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
|
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
|
||||||
|
|
||||||
if (remoteCache != null) {
|
if (remoteCache != null) {
|
||||||
|
|
|
@ -38,6 +38,8 @@ import java.util.UUID;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||||
|
|
||||||
public class ClientSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> {
|
public class ClientSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(ClientSessionPersistentChangelogBasedTransaction.class);
|
private static final Logger LOG = Logger.getLogger(ClientSessionPersistentChangelogBasedTransaction.class);
|
||||||
|
@ -55,7 +57,7 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||||
ArrayBlockingQueue<PersistentUpdate> batchingQueue,
|
ArrayBlockingQueue<PersistentUpdate> batchingQueue,
|
||||||
SerializeExecutionsByKey<UUID> serializerOnline,
|
SerializeExecutionsByKey<UUID> serializerOnline,
|
||||||
SerializeExecutionsByKey<UUID> serializerOffline) {
|
SerializeExecutionsByKey<UUID> serializerOffline) {
|
||||||
super(session, cache, offlineCache, remoteCacheInvoker, lifespanMsLoader, maxIdleTimeMsLoader, offlineLifespanMsLoader, offlineMaxIdleTimeMsLoader, batchingQueue, serializerOnline, serializerOffline);
|
super(session, CLIENT_SESSION_CACHE_NAME, cache, offlineCache, remoteCacheInvoker, lifespanMsLoader, maxIdleTimeMsLoader, offlineLifespanMsLoader, offlineMaxIdleTimeMsLoader, batchingQueue, serializerOnline, serializerOffline);
|
||||||
this.userSessionTx = userSessionTx;
|
this.userSessionTx = userSessionTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +65,10 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||||
SessionUpdatesList<AuthenticatedClientSessionEntity> myUpdates = getUpdates(offline).get(key);
|
SessionUpdatesList<AuthenticatedClientSessionEntity> myUpdates = getUpdates(offline).get(key);
|
||||||
if (myUpdates == null) {
|
if (myUpdates == null) {
|
||||||
SessionEntityWrapper<AuthenticatedClientSessionEntity> wrappedEntity = null;
|
SessionEntityWrapper<AuthenticatedClientSessionEntity> wrappedEntity = null;
|
||||||
wrappedEntity = getCache(offline).get(key);
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> cache = getCache(offline);
|
||||||
|
if (cache != null) {
|
||||||
|
wrappedEntity = cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
if (wrappedEntity == null) {
|
if (wrappedEntity == null) {
|
||||||
LOG.debugf("client-session not found in cache for sessionId=%s, offline=%s, loading from persister", key, offline);
|
LOG.debugf("client-session not found in cache for sessionId=%s, offline=%s, loading from persister", key, offline);
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.changes;
|
||||||
|
|
||||||
import org.infinispan.Cache;
|
import org.infinispan.Cache;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.Profile;
|
|
||||||
import org.keycloak.models.AbstractKeycloakTransaction;
|
import org.keycloak.models.AbstractKeycloakTransaction;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -27,18 +26,15 @@ import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.infinispan.SessionFunction;
|
import org.keycloak.models.sessions.infinispan.SessionFunction;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
|
||||||
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_SESSION_CACHE_NAME;
|
|
||||||
|
|
||||||
abstract public class PersistentSessionsChangelogBasedTransaction<K, V extends SessionEntity> extends AbstractKeycloakTransaction implements SessionsChangelogBasedTransaction<K, V> {
|
abstract public class PersistentSessionsChangelogBasedTransaction<K, V extends SessionEntity> extends AbstractKeycloakTransaction implements SessionsChangelogBasedTransaction<K, V> {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(PersistentSessionsChangelogBasedTransaction.class);
|
private static final Logger LOG = Logger.getLogger(PersistentSessionsChangelogBasedTransaction.class);
|
||||||
|
@ -54,6 +50,7 @@ abstract public class PersistentSessionsChangelogBasedTransaction<K, V extends S
|
||||||
private final SessionFunction<V> offlineMaxIdleTimeMsLoader;
|
private final SessionFunction<V> offlineMaxIdleTimeMsLoader;
|
||||||
|
|
||||||
public PersistentSessionsChangelogBasedTransaction(KeycloakSession session,
|
public PersistentSessionsChangelogBasedTransaction(KeycloakSession session,
|
||||||
|
String cacheName,
|
||||||
Cache<K, SessionEntityWrapper<V>> cache,
|
Cache<K, SessionEntityWrapper<V>> cache,
|
||||||
Cache<K, SessionEntityWrapper<V>> offlineCache,
|
Cache<K, SessionEntityWrapper<V>> offlineCache,
|
||||||
RemoteCacheInvoker remoteCacheInvoker,
|
RemoteCacheInvoker remoteCacheInvoker,
|
||||||
|
@ -66,46 +63,49 @@ abstract public class PersistentSessionsChangelogBasedTransaction<K, V extends S
|
||||||
SerializeExecutionsByKey<K> serializerOffline) {
|
SerializeExecutionsByKey<K> serializerOffline) {
|
||||||
kcSession = session;
|
kcSession = session;
|
||||||
|
|
||||||
if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
|
changesPerformers = new LinkedList<>();
|
||||||
throw new IllegalStateException("Persistent user sessions are not enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! (
|
if (batchingQueue != null) {
|
||||||
cache.getName().equals(USER_SESSION_CACHE_NAME)
|
changesPerformers.add(new JpaChangesPerformer<>(cacheName, batchingQueue));
|
||||||
|| cache.getName().equals(CLIENT_SESSION_CACHE_NAME)
|
} else {
|
||||||
|| cache.getName().equals(OFFLINE_USER_SESSION_CACHE_NAME)
|
changesPerformers.add(new JpaChangesPerformer<>(cacheName, null) {
|
||||||
|| cache.getName().equals(OFFLINE_CLIENT_SESSION_CACHE_NAME)
|
@Override
|
||||||
)) {
|
public void applyChanges() {
|
||||||
throw new IllegalStateException("Cache name is not valid for persistent user sessions: " + cache.getName());
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(),
|
||||||
}
|
super::applyChangesSynchronously);
|
||||||
|
|
||||||
changesPerformers = List.of(
|
|
||||||
new JpaChangesPerformer<>(cache.getName(), batchingQueue),
|
|
||||||
new EmbeddedCachesChangesPerformer<>(cache, serializerOnline) {
|
|
||||||
@Override
|
|
||||||
public boolean shouldConsumeChange(V entity) {
|
|
||||||
return !entity.isOffline();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new EmbeddedCachesChangesPerformer<>(offlineCache, serializerOffline){
|
|
||||||
@Override
|
|
||||||
public boolean shouldConsumeChange(V entity) {
|
|
||||||
return entity.isOffline();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new RemoteCachesChangesPerformer<>(session, cache, remoteCacheInvoker) {
|
|
||||||
@Override
|
|
||||||
public boolean shouldConsumeChange(V entity) {
|
|
||||||
return !entity.isOffline();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new RemoteCachesChangesPerformer<>(session, offlineCache, remoteCacheInvoker) {
|
|
||||||
@Override
|
|
||||||
public boolean shouldConsumeChange(V entity) {
|
|
||||||
return entity.isOffline();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache != null) {
|
||||||
|
changesPerformers.add(new EmbeddedCachesChangesPerformer<>(cache, serializerOnline) {
|
||||||
|
@Override
|
||||||
|
public boolean shouldConsumeChange(V entity) {
|
||||||
|
return !entity.isOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
changesPerformers.add(new RemoteCachesChangesPerformer<>(session, cache, remoteCacheInvoker) {
|
||||||
|
@Override
|
||||||
|
public boolean shouldConsumeChange(V entity) {
|
||||||
|
return !entity.isOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offlineCache != null) {
|
||||||
|
changesPerformers.add(new EmbeddedCachesChangesPerformer<>(offlineCache, serializerOffline){
|
||||||
|
@Override
|
||||||
|
public boolean shouldConsumeChange(V entity) {
|
||||||
|
return entity.isOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
changesPerformers.add(new RemoteCachesChangesPerformer<>(session, offlineCache, remoteCacheInvoker) {
|
||||||
|
@Override
|
||||||
|
public boolean shouldConsumeChange(V entity) {
|
||||||
|
return entity.isOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.offlineCache = offlineCache;
|
this.offlineCache = offlineCache;
|
||||||
|
|
|
@ -32,6 +32,8 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||||
|
|
||||||
public class UserSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<String, UserSessionEntity> {
|
public class UserSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<String, UserSessionEntity> {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(UserSessionPersistentChangelogBasedTransaction.class);
|
private static final Logger LOG = Logger.getLogger(UserSessionPersistentChangelogBasedTransaction.class);
|
||||||
|
@ -47,14 +49,17 @@ public class UserSessionPersistentChangelogBasedTransaction extends PersistentSe
|
||||||
ArrayBlockingQueue<PersistentUpdate> batchingQueue,
|
ArrayBlockingQueue<PersistentUpdate> batchingQueue,
|
||||||
SerializeExecutionsByKey<String> serializerOnline,
|
SerializeExecutionsByKey<String> serializerOnline,
|
||||||
SerializeExecutionsByKey<String> serializerOffline) {
|
SerializeExecutionsByKey<String> serializerOffline) {
|
||||||
super(session, cache, offlineCache, remoteCacheInvoker, lifespanMsLoader, maxIdleTimeMsLoader, offlineLifespanMsLoader, offlineMaxIdleTimeMsLoader, batchingQueue, serializerOnline, serializerOffline);
|
super(session, USER_SESSION_CACHE_NAME, cache, offlineCache, remoteCacheInvoker, lifespanMsLoader, maxIdleTimeMsLoader, offlineLifespanMsLoader, offlineMaxIdleTimeMsLoader, batchingQueue, serializerOnline, serializerOffline);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionEntityWrapper<UserSessionEntity> get(RealmModel realm, String key, boolean offline) {
|
public SessionEntityWrapper<UserSessionEntity> get(RealmModel realm, String key, boolean offline) {
|
||||||
SessionUpdatesList<UserSessionEntity> myUpdates = getUpdates(offline).get(key);
|
SessionUpdatesList<UserSessionEntity> myUpdates = getUpdates(offline).get(key);
|
||||||
if (myUpdates == null) {
|
if (myUpdates == null) {
|
||||||
SessionEntityWrapper<UserSessionEntity> wrappedEntity = null;
|
SessionEntityWrapper<UserSessionEntity> wrappedEntity = null;
|
||||||
wrappedEntity = getCache(offline).get(key);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
||||||
|
if (cache != null) {
|
||||||
|
wrappedEntity = cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
if (wrappedEntity == null) {
|
if (wrappedEntity == null) {
|
||||||
LOG.debugf("user-session not found in cache for sessionId=%s offline=%s, loading from persister", key, offline);
|
LOG.debugf("user-session not found in cache for sessionId=%s offline=%s, loading from persister", key, offline);
|
||||||
|
@ -110,6 +115,10 @@ public class UserSessionPersistentChangelogBasedTransaction extends PersistentSe
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getCache(offline) == null) {
|
||||||
|
return ((PersistentUserSessionProvider) kcSession.getProvider(UserSessionProvider.class)).wrapPersistentEntity(persistentUserSession.getRealm(), offline, persistentUserSession);
|
||||||
|
}
|
||||||
|
|
||||||
LOG.debugf("Attempting to import user-session for sessionId=%s offline=%s", sessionId, offline);
|
LOG.debugf("Attempting to import user-session for sessionId=%s offline=%s", sessionId, offline);
|
||||||
SessionEntityWrapper<UserSessionEntity> ispnUserSessionEntity = ((PersistentUserSessionProvider) kcSession.getProvider(UserSessionProvider.class)).importUserSession(persistentUserSession, offline);
|
SessionEntityWrapper<UserSessionEntity> ispnUserSessionEntity = ((PersistentUserSessionProvider) kcSession.getProvider(UserSessionProvider.class)).importUserSession(persistentUserSession, offline);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.UUID;
|
||||||
|
|
||||||
import org.infinispan.client.hotrod.RemoteCache;
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -67,7 +68,7 @@ public class RemoteUserSessionProviderFactory implements UserSessionProviderFact
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported(Config.Scope config) {
|
public boolean isSupported(Config.Scope config) {
|
||||||
return InfinispanUtils.isRemoteInfinispan();
|
return InfinispanUtils.isRemoteInfinispan() && !Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class InfinispanKeyGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private <K> K generateKey(KeycloakSession session, Cache<K, ?> cache, KeyGenerator<K> keyGenerator) {
|
protected <K> K generateKey(KeycloakSession session, Cache<K, ?> cache, KeyGenerator<K> keyGenerator) {
|
||||||
String cacheName = cache.getName();
|
String cacheName = cache.getName();
|
||||||
|
|
||||||
// "wantsLocalKey" is true if route is not attached to the sticky session cookie. Without attached route, We want the key, which will be "owned" by this node.
|
// "wantsLocalKey" is true if route is not attached to the sticky session cookie. Without attached route, We want the key, which will be "owned" by this node.
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.infinispan.Cache;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.Retry;
|
import org.keycloak.common.util.Retry;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
@ -32,6 +33,7 @@ import org.keycloak.models.sessions.infinispan.changes.sessions.SessionData;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.ProfileAssume;
|
||||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
@ -63,6 +65,7 @@ public class LastSessionRefreshUnitTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLastSessionRefreshCounters() {
|
public void testLastSessionRefreshCounters() {
|
||||||
|
ProfileAssume.assumeFeatureDisabled(Profile.Feature.REMOTE_CACHE);
|
||||||
testingClient.server().run(new LastSessionRefreshServerCounterTest());
|
testingClient.server().run(new LastSessionRefreshServerCounterTest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +110,7 @@ public class LastSessionRefreshUnitTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLastSessionRefreshIntervals() {
|
public void testLastSessionRefreshIntervals() {
|
||||||
|
ProfileAssume.assumeFeatureDisabled(Profile.Feature.REMOTE_CACHE);
|
||||||
testingClient.server().run(new LastSessionRefreshServerIntervalsTest());
|
testingClient.server().run(new LastSessionRefreshServerIntervalsTest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue