Remove offline session preloading
Closes #27602 Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
7fc2269ba5
commit
62d24216e3
19 changed files with 31 additions and 492 deletions
|
@ -108,8 +108,6 @@ public class Profile {
|
|||
|
||||
CLIENT_TYPES("Client Types", Type.EXPERIMENTAL),
|
||||
|
||||
OFFLINE_SESSION_PRELOADING("Offline session preloading", Type.DEPRECATED),
|
||||
|
||||
HOSTNAME_V1("Hostname Options V1", Type.DEFAULT),
|
||||
//HOSTNAME_V2("Hostname Options V2", Type.DEFAULT, 2),
|
||||
|
||||
|
|
|
@ -24,12 +24,12 @@ A lot of effort went into polishing and improving the Quarkus distribution to ma
|
|||
* Initial support for Cross-DC
|
||||
* User-defined profiles are no longer supported but using different configuration files to achieve the same goal
|
||||
* Quickstarts updated to use the new distribution
|
||||
|
||||
== Other improvements
|
||||
|
||||
=== Offline sessions lazy loaded
|
||||
=== Offline sessions lazily loaded
|
||||
|
||||
The offline sessions are now lazily fetched from the database by default instead of preloading during the server startup.
|
||||
To change the default behavior, see link:{adminguide_link}#offline-sessions-preloading[{adminguide_name}].
|
||||
|
||||
=== Improved User Search
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ include::topics/sessions/administering.adoc[]
|
|||
include::topics/sessions/revocation.adoc[]
|
||||
include::topics/sessions/timeouts.adoc[]
|
||||
include::topics/sessions/offline.adoc[]
|
||||
include::topics/sessions/preloading.adoc[]
|
||||
include::topics/sessions/transient.adoc[]
|
||||
include::topics/assembly-roles-groups.adoc[]
|
||||
include::topics/authentication.adoc[]
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
[[offline-sessions-preloading]]
|
||||
|
||||
=== Offline sessions preloading
|
||||
|
||||
In addition to Infinispan caches, offline sessions are stored in a database which means they will be available even after server restart.
|
||||
By default, the offline sessions are not preloaded from the database into the Infinispan caches during the server startup, because this
|
||||
approach has a drawback if there are many offline sessions to be preloaded. It can significantly slow down the server startup time.
|
||||
Therefore, the offline sessions are lazily fetched from the database by default.
|
||||
|
||||
However, {project_name} can be configured to preload the offline sessions from the database into the Infinispan caches during the server startup.
|
||||
It can be achieved by setting `preloadOfflineSessionsFromDatabase` property in the `userSessions` SPI to `true`.
|
||||
|
||||
:tech_feature_name: Offline session preloading
|
||||
:tech_feature_id: offline-session-preloading
|
||||
include::../templates/deprecated.adoc[]
|
||||
|
||||
The following example shows how to configure offline sessions preloading.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --features-enabled offline-session-preloading --spi-user-sessions-infinispan-preload-offline-sessions-from-database=true
|
||||
----
|
|
@ -26,3 +26,7 @@ bin/kc.[sh|bat] --spi-connections-http-client-default-max-consumed-response-size
|
|||
= Removed a model module
|
||||
|
||||
The module `org.keycloak:keycloak-model-legacy` module was deprecated in a previous release and is removed in this release. Use the `org.keycloak:keycloak-model-storage` module instead.
|
||||
|
||||
= Removed offline session preloading
|
||||
|
||||
The old behavior to preload offline sessions at startup is now removed after it has been deprecated in the previous release.
|
||||
|
|
|
@ -39,7 +39,6 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionSpi;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.CrossDCLastSessionRefreshStore;
|
||||
|
@ -116,8 +115,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
protected final RemoteCacheInvoker remoteCacheInvoker;
|
||||
protected final InfinispanKeyGenerator keyGenerator;
|
||||
|
||||
protected final boolean loadOfflineSessionsFromDatabase;
|
||||
|
||||
protected final SessionFunction offlineSessionCacheEntryLifespanAdjuster;
|
||||
|
||||
protected final SessionFunction offlineClientSessionCacheEntryLifespanAdjuster;
|
||||
|
@ -132,7 +129,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache,
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache,
|
||||
boolean loadOfflineSessionsFromDatabase,
|
||||
SessionFunction<UserSessionEntity> offlineSessionCacheEntryLifespanAdjuster,
|
||||
SessionFunction<AuthenticatedClientSessionEntity> offlineClientSessionCacheEntryLifespanAdjuster) {
|
||||
this.session = session;
|
||||
|
@ -154,7 +150,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
this.persisterLastSessionRefreshStore = persisterLastSessionRefreshStore;
|
||||
this.remoteCacheInvoker = remoteCacheInvoker;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.loadOfflineSessionsFromDatabase = loadOfflineSessionsFromDatabase;
|
||||
this.offlineSessionCacheEntryLifespanAdjuster = offlineSessionCacheEntryLifespanAdjuster;
|
||||
this.offlineClientSessionCacheEntryLifespanAdjuster = offlineClientSessionCacheEntryLifespanAdjuster;
|
||||
|
||||
|
@ -366,7 +361,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
protected Stream<UserSessionModel> getUserSessionsStream(RealmModel realm, UserSessionPredicate predicate, boolean offline) {
|
||||
|
||||
if (offline && loadOfflineSessionsFromDatabase) {
|
||||
if (offline) {
|
||||
|
||||
// fetch the offline user-sessions from the persistence provider
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
@ -392,16 +387,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
Stream.empty();
|
||||
}
|
||||
|
||||
if (predicate.getBrokerSessionId() != null) {
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.OFFLINE_SESSION_PRELOADING)) {
|
||||
throw new RuntimeException("The deprecated offline session preloading feature is disabled in this configuration. Read the migration guide to learn more.");
|
||||
}
|
||||
// TODO add support for offline user-session lookup by brokerSessionId
|
||||
// currently it is not possible to access the brokerSessionId in offline user-session in a database agnostic way
|
||||
throw new ModelException("Dynamic database lookup for offline user-sessions by broker session ID is currently only supported for preloaded sessions. " +
|
||||
"Set preloadOfflineSessionsFromDatabase option to \"true\" in " + UserSessionSpi.NAME + " SPI in "
|
||||
+ InfinispanUserSessionProviderFactory.PROVIDER_ID + " provider to enable the lookup.");
|
||||
}
|
||||
throw new ModelException("For offline sessions, only lookup by userId and brokerUserId is supported");
|
||||
}
|
||||
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
||||
|
@ -487,7 +473,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
protected Stream<UserSessionModel> getUserSessionsStream(final RealmModel realm, ClientModel client, Integer firstResult, Integer maxResults, final boolean offline) {
|
||||
|
||||
if (offline && loadOfflineSessionsFromDatabase) {
|
||||
if (offline) {
|
||||
// fetch the actual offline user session count from the database
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
return persister.loadUserSessionsStream(realm, client, true, firstResult, maxResults);
|
||||
|
@ -569,7 +555,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
@Override
|
||||
public Map<String, Long> getActiveClientSessionStats(RealmModel realm, boolean offline) {
|
||||
|
||||
if (offline && loadOfflineSessionsFromDatabase) {
|
||||
if (offline) {
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
return persister.getUserSessionsCountsByClients(realm, true);
|
||||
}
|
||||
|
@ -589,7 +575,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
|
||||
|
||||
if (offline && loadOfflineSessionsFromDatabase) {
|
||||
if (offline) {
|
||||
// fetch the actual offline user session count from the database
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
return persister.getUserSessionsCount(realm, client, true);
|
||||
|
@ -822,15 +808,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
return getUserSession(realm, userSessionId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.OFFLINE_SESSION_PRELOADING)) {
|
||||
throw new RuntimeException("The deprecated offline session preloading feature is disabled in this configuration. Read the migration guide to learn more.");
|
||||
}
|
||||
return this.getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerSessionId(brokerSessionId), true)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserSessionModel> getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) {
|
||||
return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), true);
|
||||
|
@ -866,14 +843,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
@Override
|
||||
public Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, UserModel user) {
|
||||
|
||||
if (loadOfflineSessionsFromDatabase) {
|
||||
return getUserSessionsFromPersistenceProviderStream(realm, user, true);
|
||||
}
|
||||
|
||||
return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getOfflineSessionsCount(RealmModel realm, ClientModel client) {
|
||||
return getUserSessionsCount(realm, client, true);
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.infinispan.persistence.remote.RemoteStore;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Environment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
|
@ -39,8 +38,6 @@ import org.keycloak.models.sessions.infinispan.changes.sessions.CrossDCLastSessi
|
|||
import org.keycloak.models.sessions.infinispan.changes.sessions.CrossDCLastSessionRefreshStoreFactory;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStore;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.CacheInitializer;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.DBLockBasedCacheInitializer;
|
||||
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
|
@ -51,7 +48,6 @@ import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
|
|||
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
|
||||
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.InfinispanCacheInitializer;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
||||
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheSessionListener;
|
||||
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheSessionsLoader;
|
||||
import org.keycloak.models.sessions.infinispan.util.InfinispanKeyGenerator;
|
||||
|
@ -85,8 +81,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
|
||||
public static final String REMOVE_USER_SESSIONS_EVENT = "REMOVE_USER_SESSIONS_EVENT";
|
||||
|
||||
private boolean preloadOfflineSessionsFromDatabase;
|
||||
|
||||
private long offlineSessionCacheEntryLifespanOverride;
|
||||
|
||||
private long offlineClientSessionCacheEntryLifespanOverride;
|
||||
|
@ -118,7 +112,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
offlineSessionsCache,
|
||||
clientSessionCache,
|
||||
offlineClientSessionsCache,
|
||||
!preloadOfflineSessionsFromDatabase,
|
||||
this::deriveOfflineSessionCacheEntryLifespanMs,
|
||||
this::deriveOfflineClientSessionCacheEntryLifespanOverrideMs
|
||||
);
|
||||
|
@ -127,11 +120,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
preloadOfflineSessionsFromDatabase = config.getBoolean("preloadOfflineSessionsFromDatabase", false);
|
||||
if (preloadOfflineSessionsFromDatabase && !Profile.isFeatureEnabled(Profile.Feature.OFFLINE_SESSION_PRELOADING)) {
|
||||
throw new RuntimeException("The deprecated offline session preloading feature is disabled in this configuration. Read the migration guide to learn more.");
|
||||
}
|
||||
|
||||
offlineSessionCacheEntryLifespanOverride = config.getInt("offlineSessionCacheEntryLifespanOverride", -1);
|
||||
offlineClientSessionCacheEntryLifespanOverride = config.getInt("offlineClientSessionCacheEntryLifespanOverride", -1);
|
||||
}
|
||||
|
@ -203,31 +191,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
|
||||
if (preloadOfflineSessionsFromDatabase) {
|
||||
// only preload offline-sessions if necessary
|
||||
log.debug("Start pre-loading userSessions from persistent storage");
|
||||
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, Serializable> workCache = connections.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME);
|
||||
int defaultStateTransferTimeout = (int) (connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME)
|
||||
.getCacheConfiguration().clustering().stateTransfer().timeout() / 1000);
|
||||
|
||||
InfinispanCacheInitializer ispnInitializer = new InfinispanCacheInitializer(sessionFactory, workCache,
|
||||
new OfflinePersistentUserSessionLoader(sessionsPerSegment), "offlineUserSessions", sessionsPerSegment, maxErrors,
|
||||
getStalledTimeoutInSeconds(defaultStateTransferTimeout));
|
||||
|
||||
// DB-lock to ensure that persistent sessions are loaded from DB just on one DC. The other DCs will load them from remote cache.
|
||||
CacheInitializer initializer = new DBLockBasedCacheInitializer(session, ispnInitializer);
|
||||
|
||||
initializer.initCache();
|
||||
initializer.loadSessions();
|
||||
|
||||
log.debug("Pre-loading userSessions from persistent storage finished");
|
||||
} else {
|
||||
log.debug("Skipping pre-loading of userSessions from persistent storage");
|
||||
}
|
||||
|
||||
// Initialize persister for periodically doing bulk DB updates of lastSessionRefresh timestamps of refreshed sessions
|
||||
persisterLastSessionRefreshStore = new PersisterLastSessionRefreshStoreFactory().createAndInit(session, true);
|
||||
}
|
||||
|
@ -428,7 +391,6 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
@Override
|
||||
public Map<String, String> getOperationalInfo() {
|
||||
Map<String, String> info = new HashMap<>();
|
||||
info.put("preloadOfflineSessionsFromDatabase", Boolean.toString(preloadOfflineSessionsFromDatabase));
|
||||
info.put("offlineSessionCacheEntryLifespanOverride", Long.toString(offlineSessionCacheEntryLifespanOverride));
|
||||
info.put("offlineClientSessionCacheEntryLifespanOverride", Long.toString(offlineClientSessionCacheEntryLifespanOverride));
|
||||
return info;
|
||||
|
|
|
@ -52,12 +52,6 @@ public abstract class BaseCacheInitializer extends CacheInitializer {
|
|||
|
||||
@Override
|
||||
protected boolean isFinished() {
|
||||
// Check if we should skipLoadingSessions. This can happen if someone else already did the task (For example in cross-dc environment, it was done by different DC)
|
||||
boolean isFinishedAlready = this.sessionLoader.isFinished(this);
|
||||
if (isFinishedAlready) {
|
||||
return true;
|
||||
}
|
||||
|
||||
InitializerState state = getStateFromCache();
|
||||
return state != null && state.isFinished();
|
||||
}
|
||||
|
@ -122,8 +116,4 @@ public abstract class BaseCacheInitializer extends CacheInitializer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public Cache<String, Serializable> getWorkCache() {
|
||||
return workCache;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.models.sessions.infinispan.initializer;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.dblock.DBLockManager;
|
||||
import org.keycloak.models.dblock.DBLockProvider;
|
||||
|
||||
/**
|
||||
* Encapsulates preloading of sessions within the DB Lock. This DB-aware lock ensures that "startLoading" is done on single DC and the other DCs need to wait.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class DBLockBasedCacheInitializer extends CacheInitializer {
|
||||
|
||||
private static final Logger log = Logger.getLogger(DBLockBasedCacheInitializer.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final CacheInitializer delegate;
|
||||
|
||||
public DBLockBasedCacheInitializer(KeycloakSession session, CacheInitializer delegate) {
|
||||
this.session = session;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initCache() {
|
||||
delegate.initCache();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isFinished() {
|
||||
return delegate.isFinished();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isCoordinator() {
|
||||
return delegate.isCoordinator();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getProgressIndicator() {
|
||||
return delegate.getProgressIndicator();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getStalledTimeoutInSeconds() {
|
||||
return delegate.getStalledTimeoutInSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Just coordinator will run this. And there is DB-lock, so the delegate.startLoading() will be permitted just by the single DC
|
||||
*/
|
||||
@Override
|
||||
protected void startLoading() {
|
||||
DBLockManager dbLockManager = new DBLockManager(session);
|
||||
dbLockManager.checkForcedUnlock();
|
||||
DBLockProvider dbLock = dbLockManager.getDBLock();
|
||||
dbLock.waitForLock(DBLockProvider.Namespace.OFFLINE_SESSIONS);
|
||||
try {
|
||||
|
||||
if (isFinished()) {
|
||||
log.infof("Task already finished when DBLock retrieved");
|
||||
} else {
|
||||
delegate.startLoading();
|
||||
}
|
||||
} finally {
|
||||
dbLock.releaseLock();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.models.sessions.infinispan.initializer;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflinePersistentUserSessionLoader implements SessionLoader<OfflinePersistentLoaderContext,
|
||||
OfflinePersistentWorkerContext, OfflinePersistentWorkerResult>, Serializable {
|
||||
|
||||
// Placeholder String used in the searching conditions to identify very first session
|
||||
private static final String FIRST_SESSION_ID = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
private static final Logger log = Logger.getLogger(OfflinePersistentUserSessionLoader.class);
|
||||
|
||||
// Cross-DC aware flag
|
||||
public static final String PERSISTENT_SESSIONS_LOADED = "PERSISTENT_SESSIONS_LOADED";
|
||||
|
||||
// Just local-DC aware flag
|
||||
public static final String PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC = "PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC";
|
||||
|
||||
|
||||
private final int sessionsPerSegment;
|
||||
|
||||
public OfflinePersistentUserSessionLoader(int sessionsPerSegment) {
|
||||
this.sessionsPerSegment = sessionsPerSegment;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(KeycloakSession session) {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OfflinePersistentLoaderContext computeLoaderContext(KeycloakSession session) {
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
int sessionsCount = persister.getUserSessionsCount(true);
|
||||
|
||||
return new OfflinePersistentLoaderContext(sessionsCount, sessionsPerSegment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OfflinePersistentWorkerContext computeWorkerContext(OfflinePersistentLoaderContext loaderCtx, int segment, int workerId, OfflinePersistentWorkerResult previousResult) {
|
||||
String lastSessionId;
|
||||
if (previousResult == null) {
|
||||
lastSessionId = FIRST_SESSION_ID;
|
||||
} else {
|
||||
lastSessionId = previousResult.getLastSessionId();
|
||||
}
|
||||
|
||||
// We know the last loaded session. New workers iteration will start from this place
|
||||
return new OfflinePersistentWorkerContext(segment, workerId, lastSessionId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OfflinePersistentWorkerResult createFailedWorkerResult(OfflinePersistentLoaderContext loaderContext, OfflinePersistentWorkerContext workerContext) {
|
||||
return new OfflinePersistentWorkerResult(false, workerContext.getSegment(), workerContext.getWorkerId(), FIRST_SESSION_ID);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OfflinePersistentWorkerResult loadSessions(KeycloakSession session, OfflinePersistentLoaderContext loaderContext, OfflinePersistentWorkerContext ctx) {
|
||||
int first = ctx.getWorkerId() * sessionsPerSegment;
|
||||
|
||||
log.tracef("Loading sessions for segment=%d lastSessionId=%s first=%d", ctx.getSegment(), ctx.getLastSessionId(), (Object) first);
|
||||
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
List<UserSessionModel> sessions = persister
|
||||
.loadUserSessionsStream(first, sessionsPerSegment, true, ctx.getLastSessionId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.tracef("Sessions loaded from DB - segment=%d lastSessionId=%s", ctx.getSegment(), ctx.getLastSessionId());
|
||||
|
||||
UserSessionModel lastSession = null;
|
||||
if (!sessions.isEmpty()) {
|
||||
lastSession = sessions.get(sessions.size() - 1);
|
||||
|
||||
// Save to memory/infinispan
|
||||
session.sessions().importUserSessions(sessions, true);
|
||||
}
|
||||
|
||||
String lastSessionId = lastSession==null ? FIRST_SESSION_ID : lastSession.getId();
|
||||
|
||||
log.tracef("Sessions imported to infinispan - segment: %d, lastSessionId: %s", ctx.getSegment(), lastSessionId);
|
||||
|
||||
return new OfflinePersistentWorkerResult(true, ctx.getSegment(), ctx.getWorkerId(), lastSessionId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isFinished(BaseCacheInitializer initializer) {
|
||||
Cache<String, Serializable> workCache = initializer.getWorkCache();
|
||||
Boolean sessionsLoaded = (Boolean) workCache.get(PERSISTENT_SESSIONS_LOADED);
|
||||
|
||||
if (sessionsLoaded != null && sessionsLoaded) {
|
||||
log.debugf("Persistent sessions loaded already.");
|
||||
return true;
|
||||
} else {
|
||||
log.debugf("Persistent sessions not yet loaded.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterAllSessionsLoaded(BaseCacheInitializer initializer) {
|
||||
Cache<String, Serializable> workCache = initializer.getWorkCache();
|
||||
|
||||
// Will retry few times for the case when backup site not available in cross-dc environment.
|
||||
// The site might be taken offline automatically if "take-offline" properly configured
|
||||
Retry.executeWithBackoff((int iteration) -> {
|
||||
|
||||
try {
|
||||
// Cross-DC aware flag
|
||||
workCache
|
||||
.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP)
|
||||
.put(PERSISTENT_SESSIONS_LOADED, true);
|
||||
|
||||
} catch (HotRodClientException re) {
|
||||
log.warnf(re, "Failed to write flag PERSISTENT_SESSIONS_LOADED in iteration '%d' . Retrying", iteration);
|
||||
|
||||
// Rethrow the exception. Retry will take care of handle the exception and eventually retry the operation.
|
||||
throw re;
|
||||
}
|
||||
|
||||
}, 10, 10);
|
||||
|
||||
// Just local-DC aware flag
|
||||
workCache
|
||||
.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP, Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE)
|
||||
.put(PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC, true);
|
||||
|
||||
|
||||
log.debugf("Persistent sessions loaded successfully!");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("OfflinePersistentUserSessionLoader [ ")
|
||||
.append("sessionsPerSegment: ").append(sessionsPerSegment)
|
||||
.append(" ]")
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -83,16 +83,6 @@ public interface SessionLoader<LOADER_CONTEXT extends SessionLoader.LoaderContex
|
|||
*/
|
||||
WORKER_RESULT createFailedWorkerResult(LOADER_CONTEXT loaderContext, WORKER_CONTEXT workerContext);
|
||||
|
||||
|
||||
/**
|
||||
* This will be called on nodes to check if loading is finished. It allows loader to notify that loading is finished for some reason.
|
||||
*
|
||||
* @param initializer
|
||||
* @return
|
||||
*/
|
||||
boolean isFinished(BaseCacheInitializer initializer);
|
||||
|
||||
|
||||
/**
|
||||
* Callback triggered on cluster coordinator once it recognize that all sessions were successfully loaded
|
||||
*
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFa
|
|||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.BaseCacheInitializer;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
|
||||
|
||||
/**
|
||||
|
@ -169,26 +168,6 @@ public class RemoteCacheSessionsLoader implements SessionLoader<RemoteCacheSessi
|
|||
10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished(BaseCacheInitializer initializer) {
|
||||
Cache<String, Serializable> workCache = initializer.getWorkCache();
|
||||
|
||||
// Check if persistent sessions were already loaded in this DC. This is possible just for offline sessions ATM
|
||||
Boolean sessionsLoaded = (Boolean) workCache
|
||||
.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE)
|
||||
.get(OfflinePersistentUserSessionLoader.PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC);
|
||||
|
||||
if ((cacheName.equals(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) || (cacheName.equals(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME)))
|
||||
&& sessionsLoaded != null && sessionsLoaded) {
|
||||
log.debugf("Sessions already loaded in current DC. Skip sessions loading from remote cache '%s'", cacheName);
|
||||
return true;
|
||||
} else {
|
||||
log.debugf("Sessions maybe not yet loaded in current DC. Will load them from remote cache '%s'", cacheName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterAllSessionsLoaded(BaseCacheInitializer initializer) {
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.keycloak.provider.Provider;
|
|||
* one cluster node at a time.</p>
|
||||
*
|
||||
* <p>There are different namespaces that can be locked. The same <em>DBLockProvider</em>
|
||||
* (same session in keycloack) can only be used to lock one namespace, a second
|
||||
* (same session in keycloak) can only be used to lock one namespace, a second
|
||||
* attempt will throw a <em>RuntimeException</em>. The <em>hasLock</em> method
|
||||
* returns the local namespace locked by this provider.</p>
|
||||
*
|
||||
|
@ -43,8 +43,9 @@ public interface DBLockProvider extends Provider {
|
|||
enum Namespace {
|
||||
|
||||
DATABASE(1),
|
||||
KEYCLOAK_BOOT(1000),
|
||||
OFFLINE_SESSIONS(1001);
|
||||
KEYCLOAK_BOOT(1000)
|
||||
// OFFLINE_SESSIONS(1001) -- Not used anymore. Keeping to avoid reusing the number.
|
||||
;
|
||||
|
||||
private final int id;
|
||||
|
||||
|
@ -55,7 +56,7 @@ public interface DBLockProvider extends Provider {
|
|||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to retrieve DB lock or wait if retrieve was unsuccessful.
|
||||
|
|
|
@ -175,16 +175,6 @@ public interface UserSessionProvider extends Provider {
|
|||
*/
|
||||
Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, UserModel user);
|
||||
|
||||
/**
|
||||
* Search user sessions by the broker session ID.
|
||||
* @deprecated
|
||||
* Instead of this method, use {@link #getOfflineUserSessionByBrokerUserIdStream(RealmModel, String)} to first get
|
||||
* the sessions of a user, and then filter by the broker session ID as needed.
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
UserSessionModel getOfflineUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
|
||||
|
||||
/**
|
||||
* Obtains the offline user sessions associated with the user that matches the specified {@code brokerUserId}.
|
||||
*
|
||||
|
|
|
@ -23,9 +23,7 @@ import static org.keycloak.services.resources.LoginActionsService.SESSION_CODE;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.TokenVerifier;
|
||||
|
@ -116,9 +114,6 @@ public class LogoutEndpoint {
|
|||
private final EventBuilder event;
|
||||
private final OIDCProviderConfig providerConfig;
|
||||
|
||||
// When enabled we cannot search offline sessions by brokerSessionId. We need to search by federated userId and then filter by brokerSessionId.
|
||||
private final boolean offlineSessionsLazyLoadingEnabled;
|
||||
|
||||
private Cors cors;
|
||||
|
||||
public LogoutEndpoint(KeycloakSession session, TokenManager tokenManager, EventBuilder event, OIDCProviderConfig providerConfig) {
|
||||
|
@ -128,10 +123,6 @@ public class LogoutEndpoint {
|
|||
this.realm = session.getContext().getRealm();
|
||||
this.event = event;
|
||||
this.providerConfig = providerConfig;
|
||||
this.offlineSessionsLazyLoadingEnabled = !Config.scope("userSessions").scope("infinispan").getBoolean("preloadOfflineSessionsFromDatabase", false);
|
||||
if (!this.offlineSessionsLazyLoadingEnabled && !Profile.isFeatureEnabled(Profile.Feature.OFFLINE_SESSION_PRELOADING)) {
|
||||
throw new RuntimeException("The deprecated offline session preloading feature is disabled in this configuration. Read the migration guide to learn more.");
|
||||
}
|
||||
this.request = session.getContext().getHttpRequest();
|
||||
this.headers = session.getContext().getRequestHeaders();
|
||||
}
|
||||
|
@ -631,11 +622,7 @@ public class LogoutEndpoint {
|
|||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, identityProviderAlias + "." + sessionId);
|
||||
|
||||
if (logoutOfflineSessions) {
|
||||
if (offlineSessionsLazyLoadingEnabled) {
|
||||
logoutOfflineUserSessionByBrokerUserId(identityProviderAlias + "." + federatedUserId, identityProviderAlias + "." + sessionId);
|
||||
} else {
|
||||
logoutOfflineUserSession(identityProviderAlias + "." + sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
if (userSession != null) {
|
||||
|
@ -646,14 +633,6 @@ public class LogoutEndpoint {
|
|||
return backchannelLogoutResponse.get();
|
||||
}
|
||||
|
||||
private void logoutOfflineUserSession(String brokerSessionId) {
|
||||
UserSessionModel offlineUserSession =
|
||||
session.sessions().getOfflineUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||
if (offlineUserSession != null) {
|
||||
new UserSessionManager(session).revokeOfflineUserSession(offlineUserSession);
|
||||
}
|
||||
}
|
||||
|
||||
private BackchannelLogoutResponse backchannelLogoutFederatedUserId(String federatedUserId,
|
||||
Stream<String> identityProviderAliases,
|
||||
boolean logoutOfflineSessions) {
|
||||
|
|
|
@ -419,7 +419,6 @@
|
|||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsInfinispan.hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}",
|
||||
"keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase": "${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase:false}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
|
@ -446,7 +445,6 @@
|
|||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsInfinispan.hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}",
|
||||
"keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase": "${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase:false}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
|
@ -474,7 +472,6 @@
|
|||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort.2:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsInfinispan.hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}",
|
||||
"keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase": "${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase:false}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
|
@ -501,7 +498,6 @@
|
|||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort.2:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsInfinispan.hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}",
|
||||
"keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase": "${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase:false}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
|
|
|
@ -29,8 +29,6 @@
|
|||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
||||
<log4j.configuration>file:${project.build.directory}/dependency/log4j.properties</log4j.configuration>
|
||||
<jacoco.skip>true</jacoco.skip>
|
||||
<keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>false</keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>
|
||||
<keycloak.profile.feature.offline_session_preloading>disabled</keycloak.profile.feature.offline_session_preloading>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -160,8 +158,6 @@
|
|||
<keycloak.connectionsJpa.default.password>${keycloak.connectionsJpa.password}</keycloak.connectionsJpa.default.password>
|
||||
<keycloak.connectionsJpa.default.url>${keycloak.connectionsJpa.url}</keycloak.connectionsJpa.default.url>
|
||||
<log4j.configuration>file:${project.build.directory}/test-classes/log4j.properties</log4j.configuration> <!-- for the logging to properly work with tests in the 'other' module -->
|
||||
<keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase}</keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>
|
||||
<keycloak.profile.feature.offline_session_preloading>${keycloak.profile.feature.offline_session_preloading}</keycloak.profile.feature.offline_session_preloading>
|
||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||
<org.jboss.logging.provider>log4j</org.jboss.logging.provider>
|
||||
<infinispan.version>${infinispan.version}</infinispan.version>
|
||||
|
@ -224,24 +220,6 @@
|
|||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>jpa+cross-dc-infinispan-offline-sessions-preloading</id>
|
||||
<properties>
|
||||
<keycloak.model.parameters>CrossDCInfinispan,Jpa</keycloak.model.parameters>
|
||||
<keycloak.profile.feature.offline_session_preloading>enabled</keycloak.profile.feature.offline_session_preloading>
|
||||
<keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>true</keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>jpa+infinispan-offline-sessions-preloading</id>
|
||||
<properties>
|
||||
<keycloak.model.parameters>Infinispan,Jpa</keycloak.model.parameters>
|
||||
<keycloak.profile.feature.offline_session_preloading>enabled</keycloak.profile.feature.offline_session_preloading>
|
||||
<keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>true</keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>jpa-federation+infinispan</id>
|
||||
<properties>
|
||||
|
|
|
@ -50,7 +50,7 @@ public class DBLockTest extends KeycloakModelTest {
|
|||
private static final int LOCK_RECHECK_MILLIS = 10;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
public void before() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
// Set timeouts for testing
|
||||
DBLockManager lockManager = new DBLockManager(session);
|
||||
|
@ -64,7 +64,7 @@ public class DBLockTest extends KeycloakModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void simpleLockTest() throws Exception {
|
||||
public void simpleLockTest() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
DBLockProvider dbLock = new DBLockManager(session).getDBLock();
|
||||
dbLock.waitForLock(DBLockProvider.Namespace.DATABASE);
|
||||
|
@ -79,7 +79,7 @@ public class DBLockTest extends KeycloakModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void simpleNestedLockTest() throws Exception {
|
||||
public void simpleNestedLockTest() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
// first session lock DATABASE
|
||||
DBLockProvider dbLock1 = new DBLockManager(session).getDBLock();
|
||||
|
@ -89,10 +89,10 @@ public class DBLockTest extends KeycloakModelTest {
|
|||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionLC2) -> {
|
||||
// a second session/dblock-provider can lock another namespace OFFLINE_SESSIONS
|
||||
DBLockProvider dbLock2 = new DBLockManager(sessionLC2).getDBLock();
|
||||
dbLock2.waitForLock(DBLockProvider.Namespace.OFFLINE_SESSIONS);
|
||||
dbLock2.waitForLock(DBLockProvider.Namespace.KEYCLOAK_BOOT);
|
||||
try {
|
||||
// getCurrentLock is local, each provider instance has one
|
||||
Assert.assertEquals(DBLockProvider.Namespace.OFFLINE_SESSIONS, dbLock2.getCurrentLock());
|
||||
Assert.assertEquals(DBLockProvider.Namespace.KEYCLOAK_BOOT, dbLock2.getCurrentLock());
|
||||
} finally {
|
||||
dbLock2.releaseLock();
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ public class DBLockTest extends KeycloakModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLockConcurrentlyGeneral() throws Exception {
|
||||
public void testLockConcurrentlyGeneral() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
testLockConcurrentlyInternal(session, DBLockProvider.Namespace.DATABASE);
|
||||
return null;
|
||||
|
@ -115,23 +115,23 @@ public class DBLockTest extends KeycloakModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLockConcurrentlyOffline() throws Exception {
|
||||
public void testLockConcurrentlyKeycloakBoot() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
testLockConcurrentlyInternal(session, DBLockProvider.Namespace.OFFLINE_SESSIONS);
|
||||
testLockConcurrentlyInternal(session, DBLockProvider.Namespace.KEYCLOAK_BOOT);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoLocksCurrently() throws Exception {
|
||||
public void testTwoLocksCurrently() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
testTwoLocksCurrentlyInternal(session, DBLockProvider.Namespace.DATABASE, DBLockProvider.Namespace.OFFLINE_SESSIONS);
|
||||
testTwoLocksCurrentlyInternal(session, DBLockProvider.Namespace.DATABASE, DBLockProvider.Namespace.KEYCLOAK_BOOT);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoNestedLocksCurrently() throws Exception {
|
||||
public void testTwoNestedLocksCurrently() {
|
||||
inComittedTransaction(1, (session , i) -> {
|
||||
testTwoNestedLocksCurrentlyInternal(session, DBLockProvider.Namespace.KEYCLOAK_BOOT, DBLockProvider.Namespace.DATABASE);
|
||||
return null;
|
||||
|
|
|
@ -37,9 +37,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionSpi;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory;
|
||||
import org.keycloak.models.utils.ResetTimeOffsetEvent;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
|
@ -338,12 +336,6 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
|||
|
||||
@Test
|
||||
public void testOfflineSessionLazyLoadingPropagationBetweenNodes() throws InterruptedException {
|
||||
// This test is only unstable after setting "keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase" to "true" and
|
||||
// CrossDC is enabled.
|
||||
// This is tracked in https://github.com/keycloak/keycloak/issues/14020 to be resolved.
|
||||
Assume.assumeFalse(Objects.equals(CONFIG.scope("userSessions.infinispan").get("preloadOfflineSessionsFromDatabase"), "true") &&
|
||||
Objects.equals(CONFIG.scope("connectionsInfinispan.default").get("remoteStoreEnabled"), "true"));
|
||||
|
||||
// as one thread fills this list and the others read it, ensure that it is synchronized to avoid side effects
|
||||
List<UserSessionModel> offlineUserSessions = Collections.synchronizedList(new LinkedList<>());
|
||||
List<AuthenticatedClientSessionModel> offlineClientSessions = Collections.synchronizedList(new LinkedList<>());
|
||||
|
@ -476,9 +468,8 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
|||
|
||||
@Test
|
||||
public void testOfflineSessionLifespanOverride() {
|
||||
// skip the test for CrossDC or when offline session preloading is enabled
|
||||
Assume.assumeFalse(Objects.equals(CONFIG.scope("userSessions.infinispan").get("preloadOfflineSessionsFromDatabase"), "true") ||
|
||||
Objects.equals(CONFIG.scope("connectionsInfinispan.default").get("remoteStoreEnabled"), "true"));
|
||||
// skip the test for CrossDC
|
||||
Assume.assumeFalse(Objects.equals(CONFIG.scope("connectionsInfinispan.default").get("remoteStoreEnabled"), "true"));
|
||||
|
||||
createOfflineSessions("user1", 2, new LinkedList<>(), new LinkedList<>());
|
||||
|
||||
|
|
Loading…
Reference in a new issue