Skip to content

Commit

Permalink
Simplify configuration for MULTI_SITE
Browse files Browse the repository at this point in the history
Closes keycloak#31807

Signed-off-by: Michal Hajas <mhajas@redhat.com>
  • Loading branch information
mhajas authored Aug 6, 2024
1 parent 3fbe26d commit 50c07c6
Show file tree
Hide file tree
Showing 32 changed files with 151 additions and 96 deletions.
1 change: 1 addition & 0 deletions common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public enum Feature {
TRANSIENT_USERS("Transient users for brokering", Type.EXPERIMENTAL),

MULTI_SITE("Multi-site support", Type.DISABLED_BY_DEFAULT),

REMOTE_CACHE("Remote caches support. Requires Multi-site support to be enabled as well.", Type.EXPERIMENTAL),

CLIENT_TYPES("Client Types", Type.EXPERIMENTAL),
Expand Down
34 changes: 34 additions & 0 deletions common/src/main/java/org/keycloak/common/util/MultiSiteUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2024 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.common.util;

import org.keycloak.common.Profile;

public class MultiSiteUtils {

public static boolean isMultiSiteEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.MULTI_SITE);
}

/**
* @return true when user sessions are stored in the database. In multi-site setup this is false when REMOTE_CACHE feature is enabled
*/
public static boolean isPersistentSessionsEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) || (isMultiSiteEnabled() && !Profile.isFeatureEnabled(Profile.Feature.REMOTE_CACHE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.infinispan.util.concurrent.ActionSequencer;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.health.LoadBalancerCheckProvider;
import org.keycloak.health.LoadBalancerCheckProviderFactory;
Expand Down Expand Up @@ -62,7 +63,7 @@ public class RemoteLoadBalancerCheckProviderFactory implements LoadBalancerCheck

@Override
public boolean isSupported(Config.Scope config) {
return InfinispanUtils.isRemoteInfinispan();
return MultiSiteUtils.isMultiSiteEnabled();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
package org.keycloak.infinispan.util;

import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.MultiSiteUtils;

import static org.keycloak.common.Profile.Feature.MULTI_SITE;
import static org.keycloak.common.Profile.Feature.REMOTE_CACHE;

public final class InfinispanUtils {
Expand All @@ -39,11 +38,11 @@ private InfinispanUtils() {

// true if running with external infinispan mode only
public static boolean isRemoteInfinispan() {
return Profile.isFeatureEnabled(Feature.MULTI_SITE) && Profile.isFeatureEnabled(REMOTE_CACHE);
return MultiSiteUtils.isMultiSiteEnabled() || Profile.isFeatureEnabled(REMOTE_CACHE);
}

// true if running with embedded caches.
public static boolean isEmbeddedInfinispan() {
return !Profile.isFeatureEnabled(MULTI_SITE) || !Profile.isFeatureEnabled(REMOTE_CACHE);
return !isRemoteInfinispan();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
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.MultiSiteUtils;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanUtil;
Expand Down Expand Up @@ -77,8 +77,6 @@
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.provider.ServerInfoAwareProviderFactory;

import static org.keycloak.common.Profile.Feature.PERSISTENT_USER_SESSIONS;

public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory<UserSessionProvider>, ServerInfoAwareProviderFactory, EnvironmentDependentProviderFactory {

private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
Expand Down Expand Up @@ -131,7 +129,7 @@ public UserSessionProvider create(KeycloakSession session) {
offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
}

if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (MultiSiteUtils.isPersistentSessionsEnabled()) {
return new PersistentUserSessionProvider(
session,
remoteCacheInvoker,
Expand Down Expand Up @@ -175,8 +173,9 @@ public void init(Config.Scope config) {
offlineSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
offlineClientSessionCacheEntryLifespanOverride = config.getInt(CONFIG_OFFLINE_CLIENT_SESSION_CACHE_ENTRY_LIFESPAN_OVERRIDE, -1);
maxBatchSize = config.getInt(CONFIG_MAX_BATCH_SIZE, DEFAULT_MAX_BATCH_SIZE);
// Do not use caches for sessions if explicitly disabled or if embedded caches are not used
useCaches = config.getBoolean(CONFIG_USE_CACHES, DEFAULT_USE_CACHES) && InfinispanUtils.isEmbeddedInfinispan();
useBatches = config.getBoolean(CONFIG_USE_BATCHES, DEFAULT_USE_BATCHES) && Profile.isFeatureEnabled(PERSISTENT_USER_SESSIONS);
useBatches = config.getBoolean(CONFIG_USE_BATCHES, DEFAULT_USE_BATCHES) && MultiSiteUtils.isPersistentSessionsEnabled();
if (useBatches) {
asyncQueuePersistentUpdate = new ArrayBlockingQueue<>(1000);
}
Expand Down Expand Up @@ -204,7 +203,7 @@ protected <K> K generateKey(KeycloakSession session, Cache<K, ?> cache, KeyGener

keyGenerator = new InfinispanKeyGenerator();
checkRemoteCaches(session);
if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (!MultiSiteUtils.isPersistentSessionsEnabled()) {
initializeLastSessionRefreshStore(factory);
}
registerClusterListeners(session);
Expand Down Expand Up @@ -237,7 +236,7 @@ protected <K> K generateKey(KeycloakSession session, Cache<K, ?> cache, KeyGener
}
}
});
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) && useBatches) {
if (MultiSiteUtils.isPersistentSessionsEnabled() && useBatches) {
persistentSessionsWorker = new PersistentSessionsWorker(factory,
asyncQueuePersistentUpdate,
maxBatchSize);
Expand Down Expand Up @@ -367,7 +366,7 @@ private <K, V extends SessionEntity> RemoteCache checkRemoteCache(KeycloakSessio
remoteCacheInvoker.addRemoteCache(ispnCache.getName(), remoteCache, maxIdleLoader);

Runnable onFailover = null;
if (useCaches && Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (useCaches && MultiSiteUtils.isPersistentSessionsEnabled()) {
// If persistent sessions are enabled, we want to clear the local caches when a failover of the listener on the remote store changes as we might have missed some of the remote store events
// which might have been triggered by another Keycloak site connected to the same remote Infinispan cluster.
// Due to this, we can be sure that we never have outdated information in our local cache. All entries will be re-loaded from the remote cache or the database as necessary lazily.
Expand Down Expand Up @@ -465,7 +464,7 @@ public int order() {

@Override
public boolean isSupported(Config.Scope config) {
return InfinispanUtils.isEmbeddedInfinispan() || Profile.isFeatureEnabled(PERSISTENT_USER_SESSIONS);
return InfinispanUtils.isEmbeddedInfinispan() || MultiSiteUtils.isPersistentSessionsEnabled();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.common.util.Retry;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
Expand Down Expand Up @@ -134,7 +135,7 @@ public PersistentUserSessionProvider(KeycloakSession session,
SerializeExecutionsByKey<String> serializerOfflineSession,
SerializeExecutionsByKey<UUID> serializerClientSession,
SerializeExecutionsByKey<UUID> serializerOfflineClientSession) {
if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (!MultiSiteUtils.isPersistentSessionsEnabled()) {
throw new IllegalStateException("Persistent user sessions are not enabled");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

package org.keycloak.models.sessions.infinispan;

import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
Expand Down Expand Up @@ -231,7 +231,7 @@ public void setLastSessionRefresh(int lastSessionRefresh) {
return;
}

if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) && offline) {
if (!MultiSiteUtils.isPersistentSessionsEnabled() && offline) {
// Received the message from the other DC that we should update the lastSessionRefresh in local cluster. Don't update DB in that case.
// The other DC already did.
Boolean ignoreRemoteCacheUpdate = (Boolean) session.getAttribute(CrossDCLastSessionRefreshListener.IGNORE_REMOTE_CACHE_UPDATE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;

/**
Expand Down Expand Up @@ -71,14 +71,14 @@ public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrappe
public abstract int hashCode();

public boolean isOffline() {
if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (!MultiSiteUtils.isPersistentSessionsEnabled()) {
throw new IllegalArgumentException("Offline flags are not supported in non-persistent-session environments.");
}
return isOffline;
}

public void setOffline(boolean offline) {
if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (!MultiSiteUtils.isPersistentSessionsEnabled()) {
throw new IllegalArgumentException("Offline flags are not supported in non-persistent-session environments.");
}
isOffline = offline;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.infinispan.util.InfinispanUtils;
import org.keycloak.models.KeycloakSession;
Expand Down Expand Up @@ -69,7 +69,7 @@ public String getId() {

@Override
public boolean isSupported(Config.Scope config) {
return InfinispanUtils.isRemoteInfinispan() && !Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS);
return InfinispanUtils.isRemoteInfinispan() && !MultiSiteUtils.isPersistentSessionsEnabled();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package org.keycloak.models.sessions.infinispan.remotestore;

import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.common.util.Retry;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -152,7 +152,7 @@ private <K, V extends SessionEntity> void replace(TopologyInfo topology, RemoteC

VersionedValue<SessionEntityWrapper<V>> versioned = remoteCache.getWithMetadata(key);
if (versioned == null) {
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) &&
if (MultiSiteUtils.isPersistentSessionsEnabled() &&
(remoteCache.getName().equals(USER_SESSION_CACHE_NAME)
|| remoteCache.getName().equals(CLIENT_SESSION_CACHE_NAME)
|| remoteCache.getName().equals(OFFLINE_USER_SESSION_CACHE_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package org.keycloak.models.jpa.session;

import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
Expand Down Expand Up @@ -253,7 +253,7 @@ public void removeExpired(RealmModel realm) {

expire(realm, expiredClientOffline, expiredOffline, true);

if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (MultiSiteUtils.isPersistentSessionsEnabled()) {

int expired = Time.currentTime() - Math.max(realm.getSsoSessionIdleTimeout(), realm.getSsoSessionIdleTimeoutRememberMe()) - SessionTimeoutHelper.PERIODIC_CLEANER_IDLE_TIMEOUT_WINDOW_SECONDS;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package org.keycloak.models.session;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
Expand Down Expand Up @@ -216,7 +216,7 @@ public RealmModel getRealm() {

@Override
public String getLoginUsername() {
if (isOffline() || !Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (isOffline() || !MultiSiteUtils.isPersistentSessionsEnabled()) {
return getUser().getUsername();
} else {
return getData().getLoginUsername();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.keycloak.authorization.policy.provider.js.DeployedScriptPolicyFactory;
import org.keycloak.common.Profile;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.config.DatabaseOptions;
import org.keycloak.config.HealthOptions;
Expand Down Expand Up @@ -644,7 +645,7 @@ void configureResteasy(CombinedIndexBuildItem index,
JsResource.class.getName())), false));
}

if (!Profile.isFeatureEnabled(Profile.Feature.MULTI_SITE)) {
if (!MultiSiteUtils.isMultiSiteEnabled()) {
buildTimeConditionBuildItemBuildProducer.produce(new BuildTimeConditionBuildItem(index.getIndex().getClassByName(DotName.createSimple(
LoadBalancerResource.class.getName())), false));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.jgroups.util.TLS;
import org.jgroups.util.TLSClientAuth;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.config.CachingOptions;
import org.keycloak.config.MetricsOptions;
import org.keycloak.infinispan.util.InfinispanUtils;
Expand Down Expand Up @@ -297,7 +298,7 @@ private CompletableFuture<DefaultCacheManager> startEmbeddedCacheManager(String
Arrays.stream(CLUSTERED_CACHE_NAMES).forEach(cacheName -> {
if (cacheName.equals(USER_SESSION_CACHE_NAME) || cacheName.equals(CLIENT_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_USER_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_CLIENT_SESSION_CACHE_NAME)) {
ConfigurationBuilder configurationBuilder = builder.getNamedConfigurationBuilders().get(cacheName);
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
if (MultiSiteUtils.isPersistentSessionsEnabled()) {
if (configurationBuilder.memory().maxCount() == -1) {
logger.infof("Persistent user sessions enabled and no memory limit found in configuration. Setting max entries for %s to 10000 entries.", cacheName);
configurationBuilder.memory().maxCount(10000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,4 @@ public void run() {
distribution.stop();
}
}

@Test
@Launch({ "start-dev", "--features=multi-site" })
void testLoadBalancerCheck(KeycloakDistribution distribution) {
distribution.setRequestPort(8080);

when().get("/lb-check").then()
.statusCode(200);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,20 @@
public class ExternalInfinispanTest {

@Test
@Launch({ "start-dev", "--features=multi-site", "--cache=ispn", "--cache-config-file=../../../test-classes/ExternalInfinispan/kcb-infinispan-cache-remote-store-config.xml", "--spi-connections-infinispan-quarkus-site-name=ISPN" })
void testLoadBalancerCheckFailure() {
@Launch({
"start-dev",
"--features=multi-site",
"--cache=ispn",
"--cache-remote-host=localhost",
"--cache-remote-username=keycloak",
"--cache-remote-password=Password1!",
"--cache-remote-tls-enabled=false",
"--spi-connections-infinispan-quarkus-site-name=ISPN",
"--spi-load-balancer-check-remote-poll-interval=500",
"-Dkc.cache-remote-create-caches=true",
"--verbose"
})
void testLoadBalancerCheckFailureWithMultiSite() {
runLoadBalancerCheckFailureTest();
}

Expand All @@ -47,9 +59,10 @@ void testLoadBalancerCheckFailure() {
"--cache-remote-host=localhost",
"--cache-remote-username=keycloak",
"--cache-remote-password=Password1!",
"--cache-remote-tls-enabled=false",
"--spi-connections-infinispan-quarkus-site-name=ISPN",
"--spi-load-balancer-check-remote-poll-interval=500",
"-Dkc.cache-remote-tls-enabled=false",
"-Dkc.cache-remote-create-caches=true",
"--verbose"
})
void testLoadBalancerCheckFailureWithRemoteOnlyCaches() {
Expand All @@ -71,7 +84,12 @@ private void runLoadBalancerCheckFailureTest() {
}

@Test
@Launch({ "start-dev", "--features=multi-site", "--cache=ispn", "--cache-config-file=../../../test-classes/ExternalInfinispan/kcb-infinispan-cache-remote-store-config.xml", "-Djboss.site.name=ISPN" })
@Launch({
"start-dev",
"--cache=ispn",
"-Djboss.site.name=ISPN",
"--verbose"
})
void testSiteNameAsSystemProperty(LaunchResult result) {
((CLIResult) result).assertMessage("System property jboss.site.name is in use. Use --spi-connections-infinispan-quarkus-site-name config option instead");
}
Expand Down
Loading

0 comments on commit 50c07c6

Please sign in to comment.