diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index f8e85327ad..7bc571d4e5 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -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), diff --git a/docs/documentation/release_notes/topics/17_0_0.adoc b/docs/documentation/release_notes/topics/17_0_0.adoc index 99b71242a8..a20bfe97e8 100644 --- a/docs/documentation/release_notes/topics/17_0_0.adoc +++ b/docs/documentation/release_notes/topics/17_0_0.adoc @@ -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 diff --git a/docs/documentation/server_admin/topics.adoc b/docs/documentation/server_admin/topics.adoc index fcef802a05..424d357066 100644 --- a/docs/documentation/server_admin/topics.adoc +++ b/docs/documentation/server_admin/topics.adoc @@ -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[] diff --git a/docs/documentation/server_admin/topics/sessions/preloading.adoc b/docs/documentation/server_admin/topics/sessions/preloading.adoc deleted file mode 100644 index c0d4f6f359..0000000000 --- a/docs/documentation/server_admin/topics/sessions/preloading.adoc +++ /dev/null @@ -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 ----- diff --git a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc index 9a59da934c..6d9e4d8ebf 100644 --- a/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc @@ -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. diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index c1ce04521a..de604041fc 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -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> offlineSessionCache, Cache> clientSessionCache, Cache> offlineClientSessionCache, - boolean loadOfflineSessionsFromDatabase, SessionFunction offlineSessionCacheEntryLifespanAdjuster, SessionFunction 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 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> cache = getCache(offline); @@ -487,7 +473,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { protected Stream 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 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 getOfflineUserSessionByBrokerUserIdStream(RealmModel realm, String brokerUserId) { return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).brokerUserId(brokerUserId), true); @@ -866,12 +843,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { @Override public Stream getOfflineUserSessionsStream(RealmModel realm, UserModel user) { - - if (loadOfflineSessionsFromDatabase) { - return getUserSessionsFromPersistenceProviderStream(realm, user, true); - } - - return getUserSessionsStream(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), true); + return getUserSessionsFromPersistenceProviderStream(realm, user, true); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java index f3735878cb..7bc470a1a5 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java @@ -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 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 getOperationalInfo() { Map info = new HashMap<>(); - info.put("preloadOfflineSessionsFromDatabase", Boolean.toString(preloadOfflineSessionsFromDatabase)); info.put("offlineSessionCacheEntryLifespanOverride", Long.toString(offlineSessionCacheEntryLifespanOverride)); info.put("offlineClientSessionCacheEntryLifespanOverride", Long.toString(offlineClientSessionCacheEntryLifespanOverride)); return info; diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java index 10fc37044d..2c3a40f4ae 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/BaseCacheInitializer.java @@ -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 getWorkCache() { - return workCache; - } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/DBLockBasedCacheInitializer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/DBLockBasedCacheInitializer.java deleted file mode 100644 index 943e1d0564..0000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/DBLockBasedCacheInitializer.java +++ /dev/null @@ -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 Marek Posolda - */ -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(); - } - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflinePersistentUserSessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflinePersistentUserSessionLoader.java deleted file mode 100644 index 83d5f1209b..0000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflinePersistentUserSessionLoader.java +++ /dev/null @@ -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 Marek Posolda - */ -public class OfflinePersistentUserSessionLoader implements SessionLoader, 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 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 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 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(); - } - -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java index b814d2ea96..af513fcfdb 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java @@ -83,16 +83,6 @@ public interface SessionLoader 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) { } diff --git a/model/storage-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java b/model/storage-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java index b0a9639430..6031a8b45d 100644 --- a/model/storage-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java +++ b/model/storage-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java @@ -24,7 +24,7 @@ import org.keycloak.provider.Provider; * one cluster node at a time.

* *

There are different namespaces that can be locked. The same DBLockProvider - * (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 RuntimeException. The hasLock method * returns the local namespace locked by this provider.

* @@ -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. diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java index dbde310353..9ff68a5559 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java @@ -175,16 +175,6 @@ public interface UserSessionProvider extends Provider { */ Stream 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}. * diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 956014f196..392c5c4296 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -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); - } + logoutOfflineUserSessionByBrokerUserId(identityProviderAlias + "." + federatedUserId, 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 identityProviderAliases, boolean logoutOfflineSessions) { 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 54a0486a05..eef4d00414 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml @@ -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:}" diff --git a/testsuite/model/pom.xml b/testsuite/model/pom.xml index 7f8ae395ef..31d286df09 100644 --- a/testsuite/model/pom.xml +++ b/testsuite/model/pom.xml @@ -29,8 +29,6 @@ ${h2.version} file:${project.build.directory}/dependency/log4j.properties true - false - disabled @@ -160,8 +158,6 @@ ${keycloak.connectionsJpa.password} ${keycloak.connectionsJpa.url} file:${project.build.directory}/test-classes/log4j.properties - ${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase} - ${keycloak.profile.feature.offline_session_preloading} org.jboss.logmanager.LogManager log4j ${infinispan.version} @@ -224,24 +220,6 @@ - - jpa+cross-dc-infinispan-offline-sessions-preloading - - CrossDCInfinispan,Jpa - enabled - true - - - - - jpa+infinispan-offline-sessions-preloading - - Infinispan,Jpa - enabled - true - - - jpa-federation+infinispan diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java index 2cd4f091a6..65da7705f4 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/DBLockTest.java @@ -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; diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java index 48f42523e5..b48dccdb2d 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java @@ -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 offlineUserSessions = Collections.synchronizedList(new LinkedList<>()); List 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<>());