Merge pull request #4563 from hmlnarik/KEYCLOAK-5656-Transport-factory-should-not-limit-to-a-single-DC-3
KEYCLOAK-5656 Use standard infinispan remote-store
This commit is contained in:
commit
fe76b2428b
23 changed files with 211 additions and 473 deletions
|
@ -116,65 +116,63 @@ Keycloak servers setup
|
||||||
<cache-container name="keycloak" jndi-name="infinispan/Keycloak" module="org.keycloak.keycloak-model-infinispan">
|
<cache-container name="keycloak" jndi-name="infinispan/Keycloak" module="org.keycloak.keycloak-model-infinispan">
|
||||||
```
|
```
|
||||||
|
|
||||||
3.3) Add the `store` under `work` cache:
|
3.3) Add the `remote-store` under `work` cache:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<replicated-cache name="work" mode="SYNC">
|
<replicated-cache name="work" mode="SYNC">
|
||||||
<store class="org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
<remote-store cache="work" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
||||||
<property name="rawValues">true</property>
|
<property name="rawValues">true</property>
|
||||||
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
||||||
<property name="remoteCacheName">work</property>
|
</remote-store>
|
||||||
<property name="sessionCache">false</property>
|
|
||||||
</store>
|
|
||||||
</replicated-cache>
|
</replicated-cache>
|
||||||
```
|
```
|
||||||
|
|
||||||
3.5) Add the `store` like this under `sessions` cache:
|
3.5) Add the `remote-store` like this under `sessions` cache:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<distributed-cache name="sessions" mode="SYNC" owners="1">
|
<distributed-cache name="sessions" mode="SYNC" owners="1">
|
||||||
<store class="org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
<remote-store cache="sessions" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
||||||
<property name="remoteCacheName">sessions</property>
|
<property name="rawValues">true</property>
|
||||||
<property name="useConfigTemplateFromCache">work</property>
|
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
||||||
<property name="sessionCache">true</property>
|
</remote-store>
|
||||||
</store>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
```
|
```
|
||||||
|
|
||||||
3.6) Same for `offlineSessions` and `loginFailures` caches (The only difference from `sessions` cache is, that `remoteCacheName` property value are different:
|
3.6) Same for `offlineSessions`, `loginFailures`, and `actionTokens` caches (the only difference from `sessions` cache is that `cache` property value are different):
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<distributed-cache name="offlineSessions" mode="SYNC" owners="1">
|
<distributed-cache name="offlineSessions" mode="SYNC" owners="1">
|
||||||
<store class="org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
<remote-store cache="offlineSessions" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
||||||
<property name="remoteCacheName">offlineSessions</property>
|
<property name="rawValues">true</property>
|
||||||
<property name="useConfigTemplateFromCache">work</property>
|
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
||||||
<property name="sessionCache">true</property>
|
</remote-store>
|
||||||
</store>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
<distributed-cache name="loginFailures" mode="SYNC" owners="1">
|
<distributed-cache name="loginFailures" mode="SYNC" owners="1">
|
||||||
<store class="org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
<remote-store cache="loginFailures" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
|
||||||
<property name="remoteCacheName">loginFailures</property>
|
<property name="rawValues">true</property>
|
||||||
<property name="useConfigTemplateFromCache">work</property>
|
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
||||||
<property name="sessionCache">true</property>
|
</remote-store>
|
||||||
</store>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
```
|
|
||||||
|
|
||||||
3.7) The configuration of `actionTokens` cache have different `remoteCacheName`, `sessionCache` and the `preload` attribute:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<distributed-cache name="actionTokens" mode="SYNC" owners="2">
|
<distributed-cache name="actionTokens" mode="SYNC" owners="2">
|
||||||
<eviction max-entries="-1" strategy="NONE"/>
|
<eviction max-entries="-1" strategy="NONE"/>
|
||||||
<expiration max-idle="-1" interval="300000"/>
|
<expiration max-idle="-1" interval="300000"/>
|
||||||
<store class="org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder" passivation="false" fetch-state="false" purge="false" preload="true" shared="true">
|
<remote-store cache="actionTokens" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="true" shared="true">
|
||||||
<property name="remoteCacheName">actionTokens</property>
|
<property name="rawValues">true</property>
|
||||||
<property name="useConfigTemplateFromCache">work</property>
|
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
|
||||||
<property name="sessionCache">false</property>
|
</remote-store>
|
||||||
</store>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3.7) Add outbound socket binding for the remote store into `socket-binding-group` configuration:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<outbound-socket-binding name="remote-cache">
|
||||||
|
<remote-destination host="${remote.cache.host:localhost}" port="${remote.cache.port:11222}"/>
|
||||||
|
</outbound-socket-binding>
|
||||||
|
```
|
||||||
|
|
||||||
3.8) The configuration of distributed cache `authenticationSessions` and other caches is left unchanged.
|
3.8) The configuration of distributed cache `authenticationSessions` and other caches is left unchanged.
|
||||||
|
|
||||||
3.9) Optionally enable DEBUG logging under `logging` subsystem:
|
3.9) Optionally enable DEBUG logging under `logging` subsystem:
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
*/
|
*/
|
||||||
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
||||||
|
|
||||||
private EmbeddedCacheManager cacheManager;
|
private final EmbeddedCacheManager cacheManager;
|
||||||
private final String siteName;
|
private final String siteName;
|
||||||
private final String nodeName;
|
private final String nodeName;
|
||||||
|
|
||||||
|
|
|
@ -41,9 +41,9 @@ import org.keycloak.Config;
|
||||||
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
|
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder;
|
|
||||||
|
|
||||||
import javax.naming.InitialContext;
|
import javax.naming.InitialContext;
|
||||||
|
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -157,7 +157,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
this.nodeName = generateNodeName();
|
this.nodeName = generateNodeName();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
|
logger.debugv("Using container managed Infinispan cache container, lookup={0}", cacheContainerLookup);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to retrieve cache container", e);
|
throw new RuntimeException("Failed to retrieve cache container", e);
|
||||||
}
|
}
|
||||||
|
@ -354,8 +354,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
|
|
||||||
builder.persistence()
|
builder.persistence()
|
||||||
.passivation(false)
|
.passivation(false)
|
||||||
.addStore(KeycloakRemoteStoreConfigurationBuilder.class)
|
.addStore(RemoteStoreConfigurationBuilder.class)
|
||||||
.sessionCache(sessionCache)
|
|
||||||
.fetchPersistentState(false)
|
.fetchPersistentState(false)
|
||||||
.ignoreModifications(false)
|
.ignoreModifications(false)
|
||||||
.purgeOnStartup(false)
|
.purgeOnStartup(false)
|
||||||
|
@ -382,8 +381,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
|
|
||||||
builder.persistence()
|
builder.persistence()
|
||||||
.passivation(false)
|
.passivation(false)
|
||||||
.addStore(KeycloakRemoteStoreConfigurationBuilder.class)
|
.addStore(RemoteStoreConfigurationBuilder.class)
|
||||||
.sessionCache(false)
|
|
||||||
.fetchPersistentState(false)
|
.fetchPersistentState(false)
|
||||||
.ignoreModifications(false)
|
.ignoreModifications(false)
|
||||||
.purgeOnStartup(false)
|
.purgeOnStartup(false)
|
||||||
|
|
|
@ -26,13 +26,31 @@ import org.infinispan.context.Flag;
|
||||||
*/
|
*/
|
||||||
public class CacheDecorators {
|
public class CacheDecorators {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds {@link Flag#CACHE_MODE_LOCAL} flag to the cache.
|
||||||
|
* @param cache
|
||||||
|
* @return Cache with the flag applied.
|
||||||
|
*/
|
||||||
public static <K, V> AdvancedCache<K, V> localCache(Cache<K, V> cache) {
|
public static <K, V> AdvancedCache<K, V> localCache(Cache<K, V> cache) {
|
||||||
return cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL);
|
return cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds {@link Flag#SKIP_CACHE_LOAD} and {@link Flag#SKIP_CACHE_STORE} flags to the cache.
|
||||||
|
* @param cache
|
||||||
|
* @return Cache with the flags applied.
|
||||||
|
*/
|
||||||
public static <K, V> AdvancedCache<K, V> skipCacheLoaders(Cache<K, V> cache) {
|
public static <K, V> AdvancedCache<K, V> skipCacheLoaders(Cache<K, V> cache) {
|
||||||
return cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE);
|
return cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds {@link Flag#SKIP_CACHE_STORE} flag to the cache.
|
||||||
|
* @param cache
|
||||||
|
* @return Cache with the flags applied.
|
||||||
|
*/
|
||||||
|
public static <K, V> AdvancedCache<K, V> skipCacheStore(Cache<K, V> cache) {
|
||||||
|
return cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,8 +303,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
|
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
|
||||||
|
|
||||||
if (remoteCache != null) {
|
if (remoteCache != null) {
|
||||||
UserSessionEntity remoteSessionEntity = (UserSessionEntity) remoteCache.get(id);
|
SessionEntityWrapper<UserSessionEntity> remoteSessionEntityWrapper = (SessionEntityWrapper<UserSessionEntity>) remoteCache.get(id);
|
||||||
if (remoteSessionEntity != null) {
|
if (remoteSessionEntityWrapper != null) {
|
||||||
|
UserSessionEntity remoteSessionEntity = remoteSessionEntityWrapper.getEntity();
|
||||||
log.debugf("getUserSessionWithPredicate(%s): remote cache contains session entity %s", id, remoteSessionEntity);
|
log.debugf("getUserSessionWithPredicate(%s): remote cache contains session entity %s", id, remoteSessionEntity);
|
||||||
|
|
||||||
UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline);
|
UserSessionModel remoteSessionAdapter = wrap(realm, remoteSessionEntity, offline);
|
||||||
|
@ -399,7 +400,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
FuturesHelper futures = new FuturesHelper();
|
FuturesHelper futures = new FuturesHelper();
|
||||||
|
|
||||||
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
|
// Each cluster node cleanups just local sessions, which are those owned by itself (+ few more taking l1 cache into account)
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(sessionCache);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(sessionCache);
|
||||||
|
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.events.AbstractUserSessionClusterListener;
|
import org.keycloak.models.sessions.infinispan.events.AbstractUserSessionClusterListener;
|
||||||
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
|
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
|
||||||
|
@ -204,7 +205,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
|
|
||||||
InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class);
|
InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
|
|
||||||
Cache sessionsCache = ispn.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionsCache = ispn.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
boolean sessionsRemoteCache = checkRemoteCache(session, sessionsCache, (RealmModel realm) -> {
|
boolean sessionsRemoteCache = checkRemoteCache(session, sessionsCache, (RealmModel realm) -> {
|
||||||
return realm.getSsoSessionIdleTimeout() * 1000;
|
return realm.getSsoSessionIdleTimeout() * 1000;
|
||||||
});
|
});
|
||||||
|
@ -214,7 +215,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Cache offlineSessionsCache = ispn.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = ispn.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||||
boolean offlineSessionsRemoteCache = checkRemoteCache(session, offlineSessionsCache, (RealmModel realm) -> {
|
boolean offlineSessionsRemoteCache = checkRemoteCache(session, offlineSessionsCache, (RealmModel realm) -> {
|
||||||
return realm.getOfflineSessionIdleTimeout() * 1000;
|
return realm.getOfflineSessionIdleTimeout() * 1000;
|
||||||
});
|
});
|
||||||
|
@ -223,13 +224,13 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true);
|
offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
||||||
boolean loginFailuresRemoteCache = checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> {
|
boolean loginFailuresRemoteCache = checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> {
|
||||||
return realm.getMaxDeltaTimeSeconds() * 1000;
|
return realm.getMaxDeltaTimeSeconds() * 1000;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkRemoteCache(KeycloakSession session, Cache ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader) {
|
private <K, V extends SessionEntity> boolean checkRemoteCache(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> ispnCache, RemoteCacheInvoker.MaxIdleTimeLoader maxIdleLoader) {
|
||||||
Set<RemoteStore> remoteStores = InfinispanUtil.getRemoteStores(ispnCache);
|
Set<RemoteStore> remoteStores = InfinispanUtil.getRemoteStores(ispnCache);
|
||||||
|
|
||||||
if (remoteStores.isEmpty()) {
|
if (remoteStores.isEmpty()) {
|
||||||
|
@ -238,7 +239,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||||
} else {
|
} else {
|
||||||
log.infof("Remote store configured for cache '%s'", ispnCache.getName());
|
log.infof("Remote store configured for cache '%s'", ispnCache.getName());
|
||||||
|
|
||||||
RemoteCache remoteCache = remoteStores.iterator().next().getRemoteCache();
|
RemoteCache<K, SessionEntityWrapper<V>> remoteCache = (RemoteCache) remoteStores.iterator().next().getRemoteCache();
|
||||||
|
|
||||||
remoteCacheInvoker.addRemoteCache(ispnCache.getName(), remoteCache, maxIdleLoader);
|
remoteCacheInvoker.addRemoteCache(ispnCache.getName(), remoteCache, maxIdleLoader);
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.AbstractKeycloakTransaction;
|
import org.keycloak.models.AbstractKeycloakTransaction;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.sessions.infinispan.CacheDecorators;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
|
|
||||||
|
@ -172,17 +173,17 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case REMOVE:
|
case REMOVE:
|
||||||
// Just remove it
|
// Just remove it
|
||||||
cache
|
CacheDecorators.skipCacheStore(cache)
|
||||||
.getAdvancedCache().withFlags(Flag.IGNORE_RETURN_VALUES)
|
.getAdvancedCache().withFlags(Flag.IGNORE_RETURN_VALUES)
|
||||||
.remove(key);
|
.remove(key);
|
||||||
break;
|
break;
|
||||||
case ADD:
|
case ADD:
|
||||||
cache
|
CacheDecorators.skipCacheStore(cache)
|
||||||
.getAdvancedCache().withFlags(Flag.IGNORE_RETURN_VALUES)
|
.getAdvancedCache().withFlags(Flag.IGNORE_RETURN_VALUES)
|
||||||
.put(key, sessionWrapper, task.getLifespanMs(), TimeUnit.MILLISECONDS);
|
.put(key, sessionWrapper, task.getLifespanMs(), TimeUnit.MILLISECONDS);
|
||||||
break;
|
break;
|
||||||
case ADD_IF_ABSENT:
|
case ADD_IF_ABSENT:
|
||||||
SessionEntityWrapper<V> existing = cache.putIfAbsent(key, sessionWrapper);
|
SessionEntityWrapper<V> existing = CacheDecorators.skipCacheStore(cache).putIfAbsent(key, sessionWrapper);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
logger.debugf("Existing entity in cache for key: %s . Will update it", key);
|
logger.debugf("Existing entity in cache for key: %s . Will update it", key);
|
||||||
|
|
||||||
|
@ -210,7 +211,7 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
|
||||||
SessionEntityWrapper<V> newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata());
|
SessionEntityWrapper<V> newVersionEntity = generateNewVersionAndWrapEntity(session, oldVersionEntity.getLocalMetadata());
|
||||||
|
|
||||||
// Atomic cluster-aware replace
|
// Atomic cluster-aware replace
|
||||||
replaced = cache.replace(key, oldVersionEntity, newVersionEntity);
|
replaced = CacheDecorators.skipCacheStore(cache).replace(key, oldVersionEntity, newVersionEntity);
|
||||||
|
|
||||||
// Replace fail. Need to load latest entity from cache, apply updates again and try to replace in cache again
|
// Replace fail. Need to load latest entity from cache, apply updates again and try to replace in cache again
|
||||||
if (!replaced) {
|
if (!replaced) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.infinispan.commons.marshall.Externalizer;
|
||||||
import org.infinispan.commons.marshall.MarshallUtil;
|
import org.infinispan.commons.marshall.MarshallUtil;
|
||||||
import org.infinispan.commons.marshall.SerializeWith;
|
import org.infinispan.commons.marshall.SerializeWith;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -36,11 +37,12 @@ import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
@SerializeWith(SessionEntityWrapper.ExternalizerImpl.class)
|
@SerializeWith(SessionEntityWrapper.ExternalizerImpl.class)
|
||||||
public class SessionEntityWrapper<S extends SessionEntity> {
|
public class SessionEntityWrapper<S extends SessionEntity> {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(SessionEntityWrapper.class);
|
||||||
|
|
||||||
private UUID version;
|
private UUID version;
|
||||||
private final S entity;
|
private final S entity;
|
||||||
private final Map<String, String> localMetadata;
|
private final Map<String, String> localMetadata;
|
||||||
|
|
||||||
|
|
||||||
protected SessionEntityWrapper(UUID version, Map<String, String> localMetadata, S entity) {
|
protected SessionEntityWrapper(UUID version, Map<String, String> localMetadata, S entity) {
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
throw new IllegalArgumentException("Version UUID can't be null");
|
throw new IllegalArgumentException("Version UUID can't be null");
|
||||||
|
@ -52,13 +54,34 @@ public class SessionEntityWrapper<S extends SessionEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionEntityWrapper(Map<String, String> localMetadata, S entity) {
|
public SessionEntityWrapper(Map<String, String> localMetadata, S entity) {
|
||||||
this(UUID.randomUUID(),localMetadata, entity);
|
this(UUID.randomUUID(), localMetadata, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionEntityWrapper(S entity) {
|
public SessionEntityWrapper(S entity) {
|
||||||
this(new ConcurrentHashMap<>(), entity);
|
this(new ConcurrentHashMap<>(), entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SessionEntityWrapper(S entity, boolean forTransport) {
|
||||||
|
if (! forTransport) {
|
||||||
|
throw new IllegalArgumentException("This constructor is only for transport entities");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.version = null;
|
||||||
|
this.localMetadata = null;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S extends SessionEntity> SessionEntityWrapper<S> forTransport(S entity) {
|
||||||
|
return new SessionEntityWrapper<>(entity, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionEntityWrapper<S> forTransport() {
|
||||||
|
return new SessionEntityWrapper<>(this.entity, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isForTransport() {
|
||||||
|
return this.version == null;
|
||||||
|
}
|
||||||
|
|
||||||
public UUID getVersion() {
|
public UUID getVersion() {
|
||||||
return version;
|
return version;
|
||||||
|
@ -68,16 +91,21 @@ public class SessionEntityWrapper<S extends SessionEntity> {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public S getEntity() {
|
public S getEntity() {
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLocalMetadataNote(String key) {
|
public String getLocalMetadataNote(String key) {
|
||||||
|
if (isForTransport()) {
|
||||||
|
throw new IllegalStateException("This entity is only intended for transport");
|
||||||
|
}
|
||||||
return localMetadata.get(key);
|
return localMetadata.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putLocalMetadataNote(String key, String value) {
|
public void putLocalMetadataNote(String key, String value) {
|
||||||
|
if (isForTransport()) {
|
||||||
|
throw new IllegalStateException("This entity is only intended for transport");
|
||||||
|
}
|
||||||
localMetadata.put(key, value);
|
localMetadata.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +115,9 @@ public class SessionEntityWrapper<S extends SessionEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putLocalMetadataNoteInt(String key, int value) {
|
public void putLocalMetadataNoteInt(String key, int value) {
|
||||||
|
if (isForTransport()) {
|
||||||
|
throw new IllegalStateException("This entity is only intended for transport");
|
||||||
|
}
|
||||||
localMetadata.put(key, String.valueOf(value));
|
localMetadata.put(key, String.valueOf(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,31 +153,45 @@ public class SessionEntityWrapper<S extends SessionEntity> {
|
||||||
|
|
||||||
public static class ExternalizerImpl implements Externalizer<SessionEntityWrapper> {
|
public static class ExternalizerImpl implements Externalizer<SessionEntityWrapper> {
|
||||||
|
|
||||||
|
private static final int VERSION_1 = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeObject(ObjectOutput output, SessionEntityWrapper obj) throws IOException {
|
public void writeObject(ObjectOutput output, SessionEntityWrapper obj) throws IOException {
|
||||||
MarshallUtil.marshallUUID(obj.version, output, false);
|
output.write(VERSION_1);
|
||||||
|
|
||||||
|
final boolean forTransport = obj.isForTransport();
|
||||||
|
output.writeBoolean(forTransport);
|
||||||
|
|
||||||
|
if (! forTransport) {
|
||||||
|
output.writeLong(obj.getVersion().getMostSignificantBits());
|
||||||
|
output.writeLong(obj.getVersion().getLeastSignificantBits());
|
||||||
MarshallUtil.marshallMap(obj.localMetadata, output);
|
MarshallUtil.marshallMap(obj.localMetadata, output);
|
||||||
output.writeObject(obj.getEntity());
|
}
|
||||||
|
|
||||||
|
output.writeObject(obj.entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SessionEntityWrapper readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
public SessionEntityWrapper readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||||
UUID objVersion = MarshallUtil.unmarshallUUID(input, false);
|
byte version = input.readByte();
|
||||||
|
|
||||||
Map<String, String> localMetadata = MarshallUtil.unmarshallMap(input, new MarshallUtil.MapBuilder<String, String, Map<String, String>>() {
|
if (version != VERSION_1) {
|
||||||
|
throw new IOException("Invalid version: " + version);
|
||||||
@Override
|
|
||||||
public Map<String, String> build(int size) {
|
|
||||||
return new ConcurrentHashMap<>(size);
|
|
||||||
}
|
}
|
||||||
|
final boolean forTransport = input.readBoolean();
|
||||||
|
|
||||||
});
|
if (forTransport) {
|
||||||
|
final SessionEntity entity = (SessionEntity) input.readObject();
|
||||||
SessionEntity entity = (SessionEntity) input.readObject();
|
log.debugf("Loaded entity from remote store: %s", entity);
|
||||||
|
return new SessionEntityWrapper(entity);
|
||||||
return new SessionEntityWrapper<>(objVersion, localMetadata, entity);
|
} else {
|
||||||
|
UUID sessionVersion = new UUID(input.readLong(), input.readLong());
|
||||||
|
ConcurrentHashMap<String, String> map = MarshallUtil.unmarshallMap(input, (size) -> new ConcurrentHashMap<>(size));
|
||||||
|
final SessionEntity entity = (SessionEntity) input.readObject();
|
||||||
|
log.debugf("Found entity locally: %s", entity);
|
||||||
|
return new SessionEntityWrapper(sessionVersion, map, entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
@ -43,11 +44,11 @@ public class LastSessionRefreshListener implements ClusterListener {
|
||||||
private final boolean offline;
|
private final boolean offline;
|
||||||
|
|
||||||
private final KeycloakSessionFactory sessionFactory;
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
private final Cache<String, SessionEntityWrapper> cache;
|
private final Cache<String, SessionEntityWrapper<UserSessionEntity>> cache;
|
||||||
private final boolean distributed;
|
private final boolean distributed;
|
||||||
private final String myAddress;
|
private final String myAddress;
|
||||||
|
|
||||||
public LastSessionRefreshListener(KeycloakSession session, Cache<String, SessionEntityWrapper> cache, boolean offline) {
|
public LastSessionRefreshListener(KeycloakSession session, Cache<String, SessionEntityWrapper<UserSessionEntity>> cache, boolean offline) {
|
||||||
this.sessionFactory = session.getKeycloakSessionFactory();
|
this.sessionFactory = session.getKeycloakSessionFactory();
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.offline = offline;
|
this.offline = offline;
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.cluster.ClusterProvider;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,12 +40,12 @@ public class LastSessionRefreshStoreFactory {
|
||||||
public static final int DEFAULT_MAX_COUNT = 100;
|
public static final int DEFAULT_MAX_COUNT = 100;
|
||||||
|
|
||||||
|
|
||||||
public LastSessionRefreshStore createAndInit(KeycloakSession kcSession, Cache<String, SessionEntityWrapper> cache, boolean offline) {
|
public LastSessionRefreshStore createAndInit(KeycloakSession kcSession, Cache<String, SessionEntityWrapper<UserSessionEntity>> cache, boolean offline) {
|
||||||
return createAndInit(kcSession, cache, DEFAULT_TIMER_INTERVAL_MS, DEFAULT_MAX_INTERVAL_BETWEEN_MESSAGES_SECONDS, DEFAULT_MAX_COUNT, offline);
|
return createAndInit(kcSession, cache, DEFAULT_TIMER_INTERVAL_MS, DEFAULT_MAX_INTERVAL_BETWEEN_MESSAGES_SECONDS, DEFAULT_MAX_COUNT, offline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LastSessionRefreshStore createAndInit(KeycloakSession kcSession, Cache<String, SessionEntityWrapper> cache, long timerIntervalMs, int maxIntervalBetweenMessagesSeconds, int maxCount, boolean offline) {
|
public LastSessionRefreshStore createAndInit(KeycloakSession kcSession, Cache<String, SessionEntityWrapper<UserSessionEntity>> cache, long timerIntervalMs, int maxIntervalBetweenMessagesSeconds, int maxCount, boolean offline) {
|
||||||
String eventKey = offline ? "lastSessionRefreshes-offline" : "lastSessionRefreshes";
|
String eventKey = offline ? "lastSessionRefreshes-offline" : "lastSessionRefreshes";
|
||||||
LastSessionRefreshStore store = createStoreInstance(maxIntervalBetweenMessagesSeconds, maxCount, eventKey);
|
LastSessionRefreshStore store = createStoreInstance(maxIntervalBetweenMessagesSeconds, maxCount, eventKey);
|
||||||
|
|
||||||
|
|
|
@ -1,176 +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.remotestore;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import org.infinispan.commons.CacheException;
|
|
||||||
import org.infinispan.commons.configuration.ConfiguredBy;
|
|
||||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
|
||||||
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
|
|
||||||
import org.infinispan.configuration.cache.StoreConfiguration;
|
|
||||||
import org.infinispan.filter.KeyFilter;
|
|
||||||
import org.infinispan.manager.EmbeddedCacheManager;
|
|
||||||
import org.infinispan.marshall.core.MarshalledEntry;
|
|
||||||
import org.infinispan.metadata.InternalMetadata;
|
|
||||||
import org.infinispan.persistence.InitializationContextImpl;
|
|
||||||
import org.infinispan.persistence.remote.RemoteStore;
|
|
||||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
|
|
||||||
import org.infinispan.persistence.spi.InitializationContext;
|
|
||||||
import org.infinispan.persistence.spi.PersistenceException;
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
@ConfiguredBy(KeycloakRemoteStoreConfiguration.class)
|
|
||||||
public class KeycloakRemoteStore extends RemoteStore {
|
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(KeycloakRemoteStore.class);
|
|
||||||
|
|
||||||
private String remoteCacheName;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws PersistenceException {
|
|
||||||
this.remoteCacheName = getConfiguration().remoteCacheName();
|
|
||||||
Boolean sessionCache = getConfiguration().sessionCache();
|
|
||||||
|
|
||||||
String cacheTemplateName = getConfiguration().useConfigTemplateFromCache();
|
|
||||||
|
|
||||||
if (cacheTemplateName != null) {
|
|
||||||
logger.debugf("Will override configuration of cache '%s' from template of cache '%s'", ctx.getCache().getName(), cacheTemplateName);
|
|
||||||
|
|
||||||
// Just to ensure that dependent cache is started and it's configuration fully loaded
|
|
||||||
EmbeddedCacheManager cacheManager = ctx.getCache().getCacheManager();
|
|
||||||
cacheManager.getCache(cacheTemplateName, true);
|
|
||||||
|
|
||||||
KeycloakRemoteStoreConfiguration templateConfig = (KeycloakRemoteStoreConfiguration) cacheManager.getCacheConfiguration(cacheTemplateName).persistence().stores().stream()
|
|
||||||
.filter((StoreConfiguration storeConfig) -> storeConfig instanceof KeycloakRemoteStoreConfiguration)
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new CacheException("Unable to find remoteStore on cache '" + cacheTemplateName + "."));
|
|
||||||
|
|
||||||
// We have template configuration, so create new configuration from it. Override just remoteCacheName and sessionsCache (not pretty, but works for now)
|
|
||||||
PersistenceConfigurationBuilder readPersistenceBuilder = new ConfigurationBuilder().read(ctx.getCache().getCacheConfiguration()).persistence();
|
|
||||||
KeycloakRemoteStoreConfigurationBuilder configBuilder = new KeycloakRemoteStoreConfigurationBuilder(readPersistenceBuilder);
|
|
||||||
configBuilder.read(templateConfig);
|
|
||||||
|
|
||||||
// Rather log this to clearly show in the log that this might be a configuration mistake (Note that it can be expected for some cases)
|
|
||||||
if (!this.remoteCacheName.equals(ctx.getCache().getName())) {
|
|
||||||
logger.warnf("Cache name and remoteCache name are different - maybe it's expected. Cache name '%s', remoteCache name '%s'.", ctx.getCache().getName(), this.remoteCacheName);
|
|
||||||
}
|
|
||||||
|
|
||||||
configBuilder.remoteCacheName(this.remoteCacheName);
|
|
||||||
configBuilder.sessionCache(sessionCache);
|
|
||||||
|
|
||||||
RemoteStoreConfiguration newCfg1 = configBuilder.create();
|
|
||||||
KeycloakRemoteStoreConfiguration newCfg = new KeycloakRemoteStoreConfiguration(newCfg1);
|
|
||||||
|
|
||||||
InitializationContext ctx = new InitializationContextImpl(newCfg, this.ctx.getCache(), this.ctx.getMarshaller(), this.ctx.getTimeService(),
|
|
||||||
this.ctx.getByteBufferFactory(), this.ctx.getMarshalledEntryFactory());
|
|
||||||
|
|
||||||
init(ctx);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
logger.debugf("Skip overriding configuration from template for cache '%s'", ctx.getCache().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debugf("Using configuration for remote cache '%s': %s", remoteCacheName, getConfiguration().toString());
|
|
||||||
|
|
||||||
super.start();
|
|
||||||
|
|
||||||
if (getRemoteCache() == null) {
|
|
||||||
String cacheName = getConfiguration().remoteCacheName();
|
|
||||||
throw new CacheException("Remote cache '" + cacheName + "' is not available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MarshalledEntry load(Object key) throws PersistenceException {
|
|
||||||
if (!getConfiguration().sessionCache()) {
|
|
||||||
return super.load(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debugf("Calling load: '%s' for remote cache '%s'", key, remoteCacheName);
|
|
||||||
|
|
||||||
MarshalledEntry entry = super.load(key);
|
|
||||||
if (entry == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap remote entity
|
|
||||||
SessionEntity entity = (SessionEntity) entry.getValue();
|
|
||||||
SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity);
|
|
||||||
|
|
||||||
MarshalledEntry wrappedEntry = marshalledEntry(entry.getKey(), entityWrapper, entry.getMetadata());
|
|
||||||
|
|
||||||
logger.debugf("Found entry in load: %s", wrappedEntry.toString());
|
|
||||||
|
|
||||||
return wrappedEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Don't do anything. Iterate over remoteCache.keySet() can have big performance impact. We handle bulk load by ourselves if needed.
|
|
||||||
@Override
|
|
||||||
public void process(KeyFilter filter, CacheLoaderTask task, Executor executor, boolean fetchValue, boolean fetchMetadata) {
|
|
||||||
if (!getConfiguration().sessionCache()) {
|
|
||||||
super.process(filter, task, executor, fetchValue, fetchMetadata);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debugf("Skip calling process with filter '%s' on cache '%s'", filter, remoteCacheName);
|
|
||||||
// super.process(filter, task, executor, fetchValue, fetchMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Don't do anything. Writes handled by KC itself as we need more flexibility
|
|
||||||
@Override
|
|
||||||
public void write(MarshalledEntry entry) throws PersistenceException {
|
|
||||||
if (!getConfiguration().sessionCache()) {
|
|
||||||
super.write(entry);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean delete(Object key) throws PersistenceException {
|
|
||||||
if (!getConfiguration().sessionCache()) {
|
|
||||||
return super.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debugf("Calling delete for key '%s' on cache '%s'", key, remoteCacheName);
|
|
||||||
|
|
||||||
// Optimization - we don't need to know the previous value.
|
|
||||||
// TODO: For some usecases (bulk removal of user sessions), it may be better for performance to call removeAsync and wait for all futures to be finished
|
|
||||||
getRemoteCache().remove(key);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MarshalledEntry marshalledEntry(Object key, Object value, InternalMetadata metadata) {
|
|
||||||
return ctx.getMarshalledEntryFactory().newMarshalledEntry(key, value, metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakRemoteStoreConfiguration getConfiguration() {
|
|
||||||
return (KeycloakRemoteStoreConfiguration) super.getConfiguration();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +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.remotestore;
|
|
||||||
|
|
||||||
import org.infinispan.commons.configuration.BuiltBy;
|
|
||||||
import org.infinispan.commons.configuration.ConfigurationFor;
|
|
||||||
import org.infinispan.commons.configuration.attributes.Attribute;
|
|
||||||
import org.infinispan.commons.configuration.attributes.AttributeDefinition;
|
|
||||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
@BuiltBy(KeycloakRemoteStoreConfigurationBuilder.class)
|
|
||||||
@ConfigurationFor(KeycloakRemoteStore.class)
|
|
||||||
public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
|
|
||||||
|
|
||||||
static final AttributeDefinition<String> USE_CONFIG_TEMPLATE_FROM_CACHE = AttributeDefinition.builder("useConfigTemplateFromCache", null, String.class).immutable().build();
|
|
||||||
static final AttributeDefinition<Boolean> SESSION_CACHE = AttributeDefinition.builder("sessionCache", null, Boolean.class).immutable().build();
|
|
||||||
|
|
||||||
private final Attribute<String> useConfigTemplateFromCache;
|
|
||||||
private final Attribute<Boolean> sessionCache;
|
|
||||||
|
|
||||||
|
|
||||||
public KeycloakRemoteStoreConfiguration(RemoteStoreConfiguration other) {
|
|
||||||
super(other.attributes(), other.async(), other.singletonStore(), other.asyncExecutorFactory(), other.connectionPool());
|
|
||||||
useConfigTemplateFromCache = attributes.attribute(USE_CONFIG_TEMPLATE_FROM_CACHE.name());
|
|
||||||
sessionCache = attributes.attribute(SESSION_CACHE.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String useConfigTemplateFromCache() {
|
|
||||||
return useConfigTemplateFromCache.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Boolean sessionCache() {
|
|
||||||
return sessionCache.get()==null ? false : sessionCache.get();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +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.remotestore;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.infinispan.commons.CacheConfigurationException;
|
|
||||||
import org.infinispan.commons.configuration.attributes.Attribute;
|
|
||||||
import org.infinispan.commons.configuration.attributes.AttributeDefinition;
|
|
||||||
import org.infinispan.commons.configuration.attributes.AttributeSet;
|
|
||||||
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
|
|
||||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
|
|
||||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
|
||||||
import org.keycloak.common.util.reflections.Reflections;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakRemoteStoreConfigurationBuilder extends RemoteStoreConfigurationBuilder {
|
|
||||||
|
|
||||||
public KeycloakRemoteStoreConfigurationBuilder(PersistenceConfigurationBuilder builder) {
|
|
||||||
super(builder);
|
|
||||||
|
|
||||||
// No better way to add new attribute definition to superclass :/
|
|
||||||
try {
|
|
||||||
Field f = Reflections.findDeclaredField(AttributeSet.class, "attributes");
|
|
||||||
f.setAccessible(true);
|
|
||||||
Map<String, Attribute<? extends Object>> attributesInternal = (Map<String, Attribute<? extends Object>>) f.get(this.attributes);
|
|
||||||
|
|
||||||
AttributeDefinition<String> def = KeycloakRemoteStoreConfiguration.USE_CONFIG_TEMPLATE_FROM_CACHE;
|
|
||||||
Attribute<String> attribute = def.toAttribute();
|
|
||||||
attributesInternal.put(def.name(), attribute);
|
|
||||||
|
|
||||||
AttributeDefinition<Boolean> defBool = KeycloakRemoteStoreConfiguration.SESSION_CACHE;
|
|
||||||
Attribute<Boolean> attributeBool = defBool.toAttribute();
|
|
||||||
attributesInternal.put(defBool.name(), attributeBool);
|
|
||||||
|
|
||||||
} catch (IllegalAccessException iae) {
|
|
||||||
throw new CacheConfigurationException(iae);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakRemoteStoreConfiguration create() {
|
|
||||||
RemoteStoreConfiguration cfg = super.create();
|
|
||||||
KeycloakRemoteStoreConfiguration cfg2 = new KeycloakRemoteStoreConfiguration(cfg);
|
|
||||||
return cfg2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public KeycloakRemoteStoreConfigurationBuilder useConfigTemplateFromCache(String useConfigTemplateFromCache) {
|
|
||||||
attributes.attribute(KeycloakRemoteStoreConfiguration.USE_CONFIG_TEMPLATE_FROM_CACHE).set(useConfigTemplateFromCache);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public KeycloakRemoteStoreConfigurationBuilder sessionCache(Boolean sessionCache) {
|
|
||||||
attributes.attribute(KeycloakRemoteStoreConfiguration.SESSION_CACHE).set(sessionCache);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -82,23 +82,22 @@ public class RemoteCacheInvoker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private <K, V extends SessionEntity> void runOnRemoteCache(RemoteCache<K, V> remoteCache, long maxIdleMs, K key, SessionUpdateTask<V> task, SessionEntityWrapper<V> sessionWrapper) {
|
private <K, V extends SessionEntity> void runOnRemoteCache(RemoteCache<K, SessionEntityWrapper<V>> remoteCache, long maxIdleMs, K key, SessionUpdateTask<V> task, SessionEntityWrapper<V> sessionWrapper) {
|
||||||
V session = sessionWrapper.getEntity();
|
final V session = sessionWrapper.getEntity();
|
||||||
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
SessionUpdateTask.CacheOperation operation = task.getOperation(session);
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case REMOVE:
|
case REMOVE:
|
||||||
// REMOVE already handled at remote cache store level
|
remoteCache.remove(key);
|
||||||
//remoteCache.remove(key);
|
|
||||||
break;
|
break;
|
||||||
case ADD:
|
case ADD:
|
||||||
remoteCache.put(key, session, task.getLifespanMs(), TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
remoteCache.put(key, sessionWrapper.forTransport(), task.getLifespanMs(), TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
||||||
break;
|
break;
|
||||||
case ADD_IF_ABSENT:
|
case ADD_IF_ABSENT:
|
||||||
final int currentTime = Time.currentTime();
|
final int currentTime = Time.currentTime();
|
||||||
SessionEntity existing = remoteCache
|
SessionEntityWrapper<V> existing = remoteCache
|
||||||
.withFlags(Flag.FORCE_RETURN_VALUE)
|
.withFlags(Flag.FORCE_RETURN_VALUE)
|
||||||
.putIfAbsent(key, session, -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
.putIfAbsent(key, sessionWrapper.forTransport(), -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
logger.debugf("Existing entity in remote cache for key: %s . Will update it", key);
|
logger.debugf("Existing entity in remote cache for key: %s . Will update it", key);
|
||||||
|
|
||||||
|
@ -116,23 +115,24 @@ public class RemoteCacheInvoker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private <K, V extends SessionEntity> void replace(RemoteCache<K, V> remoteCache, long lifespanMs, long maxIdleMs, K key, SessionUpdateTask<V> task) {
|
private <K, V extends SessionEntity> void replace(RemoteCache<K, SessionEntityWrapper<V>> remoteCache, long lifespanMs, long maxIdleMs, K key, SessionUpdateTask<V> task) {
|
||||||
boolean replaced = false;
|
boolean replaced = false;
|
||||||
while (!replaced) {
|
while (!replaced) {
|
||||||
VersionedValue<V> versioned = remoteCache.getVersioned(key);
|
VersionedValue<SessionEntityWrapper<V>> versioned = remoteCache.getVersioned(key);
|
||||||
if (versioned == null) {
|
if (versioned == null) {
|
||||||
logger.warnf("Not found entity to replace for key '%s'", key);
|
logger.warnf("Not found entity to replace for key '%s'", key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
V session = versioned.getValue();
|
SessionEntityWrapper<V> sessionWrapper = versioned.getValue();
|
||||||
|
final V session = sessionWrapper.getEntity();
|
||||||
|
|
||||||
// Run task on the remote session
|
// Run task on the remote session
|
||||||
task.runUpdate(session);
|
task.runUpdate(session);
|
||||||
|
|
||||||
logger.debugf("Before replaceWithVersion. Entity to write version %d: %s", versioned.getVersion(), session);
|
logger.debugf("Before replaceWithVersion. Entity to write version %d: %s", versioned.getVersion(), session);
|
||||||
|
|
||||||
replaced = remoteCache.replaceWithVersion(key, session, versioned.getVersion(), lifespanMs, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
replaced = remoteCache.replaceWithVersion(key, SessionEntityWrapper.forTransport(session), versioned.getVersion(), lifespanMs, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
if (!replaced) {
|
if (!replaced) {
|
||||||
logger.debugf("Failed to replace entity '%s' version %d. Will retry again", key, versioned.getVersion());
|
logger.debugf("Failed to replace entity '%s' version %d. Will retry again", key, versioned.getVersion());
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class RemoteCacheSessionListener<K, V extends SessionEntity> {
|
||||||
protected static final Logger logger = Logger.getLogger(RemoteCacheSessionListener.class);
|
protected static final Logger logger = Logger.getLogger(RemoteCacheSessionListener.class);
|
||||||
|
|
||||||
private Cache<K, SessionEntityWrapper<V>> cache;
|
private Cache<K, SessionEntityWrapper<V>> cache;
|
||||||
private RemoteCache<K, V> remoteCache;
|
private RemoteCache<K, SessionEntityWrapper<V>> remoteCache;
|
||||||
private boolean distributed;
|
private boolean distributed;
|
||||||
private String myAddress;
|
private String myAddress;
|
||||||
private ClientListenerExecutorDecorator<K> executor;
|
private ClientListenerExecutorDecorator<K> executor;
|
||||||
|
@ -57,7 +57,7 @@ public class RemoteCacheSessionListener<K, V extends SessionEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void init(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, V> remoteCache) {
|
protected void init(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, SessionEntityWrapper<V>> remoteCache) {
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.remoteCache = remoteCache;
|
this.remoteCache = remoteCache;
|
||||||
|
|
||||||
|
@ -113,10 +113,10 @@ public class RemoteCacheSessionListener<K, V extends SessionEntity> {
|
||||||
replaceRetries++;
|
replaceRetries++;
|
||||||
|
|
||||||
SessionEntityWrapper<V> localEntityWrapper = cache.get(key);
|
SessionEntityWrapper<V> localEntityWrapper = cache.get(key);
|
||||||
VersionedValue<V> remoteSessionVersioned = remoteCache.getVersioned(key);
|
VersionedValue<SessionEntityWrapper<V>> remoteSessionVersioned = remoteCache.getVersioned(key);
|
||||||
|
|
||||||
// Probably already removed
|
// Probably already removed
|
||||||
if (remoteSessionVersioned == null) {
|
if (remoteSessionVersioned == null || remoteSessionVersioned.getValue() == null) {
|
||||||
logger.debugf("Entity '%s' not present in remoteCache. Ignoring replace",
|
logger.debugf("Entity '%s' not present in remoteCache. Ignoring replace",
|
||||||
key.toString());
|
key.toString());
|
||||||
return;
|
return;
|
||||||
|
@ -134,7 +134,7 @@ public class RemoteCacheSessionListener<K, V extends SessionEntity> {
|
||||||
sleepInterval = sleepInterval << 1;
|
sleepInterval = sleepInterval << 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SessionEntity remoteSession = remoteSessionVersioned.getValue();
|
SessionEntity remoteSession = remoteSessionVersioned.getValue().getEntity();
|
||||||
|
|
||||||
logger.debugf("Read session entity from the remote cache: %s . replaceRetries=%d", remoteSession.toString(), replaceRetries);
|
logger.debugf("Read session entity from the remote cache: %s . replaceRetries=%d", remoteSession.toString(), replaceRetries);
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ public class RemoteCacheSessionListener<K, V extends SessionEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static <K, V extends SessionEntity> RemoteCacheSessionListener createListener(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, V> remoteCache) {
|
public static <K, V extends SessionEntity> RemoteCacheSessionListener createListener(KeycloakSession session, Cache<K, SessionEntityWrapper<V>> cache, RemoteCache<K, SessionEntityWrapper<V>> remoteCache) {
|
||||||
/*boolean isCoordinator = InfinispanUtil.isCoordinator(cache);
|
/*boolean isCoordinator = InfinispanUtil.isCoordinator(cache);
|
||||||
|
|
||||||
// Just cluster coordinator will fetch userSessions from remote cache.
|
// Just cluster coordinator will fetch userSessions from remote cache.
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.BaseCacheInitializer;
|
import org.keycloak.models.sessions.infinispan.initializer.BaseCacheInitializer;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
|
||||||
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
|
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
|
||||||
|
@ -116,9 +115,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
|
||||||
for (Map.Entry<byte[], byte[]> entry : remoteObjects.entrySet()) {
|
for (Map.Entry<byte[], byte[]> entry : remoteObjects.entrySet()) {
|
||||||
try {
|
try {
|
||||||
Object key = marshaller.objectFromByteBuffer(entry.getKey());
|
Object key = marshaller.objectFromByteBuffer(entry.getKey());
|
||||||
SessionEntity entity = (SessionEntity) marshaller.objectFromByteBuffer(entry.getValue());
|
SessionEntityWrapper entityWrapper = (SessionEntityWrapper) marshaller.objectFromByteBuffer(entry.getValue());
|
||||||
|
|
||||||
SessionEntityWrapper entityWrapper = new SessionEntityWrapper(entity);
|
|
||||||
|
|
||||||
decoratedCache.putAsync(key, entityWrapper);
|
decoratedCache.putAsync(key, entityWrapper);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -39,8 +39,8 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder;
|
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
|
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test concurrency for remoteStore (backed by HotRod RemoteCaches) against external JDG. Especially tests "replaceWithVersion" contract.
|
* Test concurrency for remoteStore (backed by HotRod RemoteCaches) against external JDG. Especially tests "replaceWithVersion" contract.
|
||||||
|
@ -207,7 +207,7 @@ public class ConcurrencyJDGSessionsCacheTest {
|
||||||
|
|
||||||
|
|
||||||
private static EmbeddedCacheManager createManager(int threadId) {
|
private static EmbeddedCacheManager createManager(int threadId) {
|
||||||
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, KeycloakRemoteStoreConfigurationBuilder.class);
|
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@ package org.keycloak.cluster.infinispan;
|
||||||
|
|
||||||
import org.infinispan.configuration.cache.Configuration;
|
import org.infinispan.configuration.cache.Configuration;
|
||||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||||
|
import org.infinispan.configuration.cache.StoreConfigurationBuilder;
|
||||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||||
import org.infinispan.manager.DefaultCacheManager;
|
import org.infinispan.manager.DefaultCacheManager;
|
||||||
import org.infinispan.manager.EmbeddedCacheManager;
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
|
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
|
||||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationChildBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -31,7 +32,7 @@ import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationB
|
||||||
class TestCacheManagerFactory {
|
class TestCacheManagerFactory {
|
||||||
|
|
||||||
|
|
||||||
<T extends RemoteStoreConfigurationBuilder> EmbeddedCacheManager createManager(int threadId, String cacheName, Class<T> builderClass) {
|
<T extends StoreConfigurationBuilder<?, T> & RemoteStoreConfigurationChildBuilder<T>> EmbeddedCacheManager createManager(int threadId, String cacheName, Class<T> builderClass) {
|
||||||
System.setProperty("java.net.preferIPv4Stack", "true");
|
System.setProperty("java.net.preferIPv4Stack", "true");
|
||||||
System.setProperty("jgroups.tcp.port", "53715");
|
System.setProperty("jgroups.tcp.port", "53715");
|
||||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||||
|
@ -57,7 +58,7 @@ class TestCacheManagerFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private <T extends RemoteStoreConfigurationBuilder> Configuration getCacheBackedByRemoteStore(int threadId, String cacheName, Class<T> builderClass) {
|
private <T extends StoreConfigurationBuilder<?, T> & RemoteStoreConfigurationChildBuilder<T>> Configuration getCacheBackedByRemoteStore(int threadId, String cacheName, Class<T> builderClass) {
|
||||||
ConfigurationBuilder cacheConfigBuilder = new ConfigurationBuilder();
|
ConfigurationBuilder cacheConfigBuilder = new ConfigurationBuilder();
|
||||||
|
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
|
|
|
@ -44,8 +44,8 @@ public class DefaultExecutorsProviderFactory implements ExecutorsProviderFactory
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(DefaultExecutorsProviderFactory.class);
|
protected static final Logger logger = Logger.getLogger(DefaultExecutorsProviderFactory.class);
|
||||||
|
|
||||||
private int DEFAULT_MIN_THREADS = 4;
|
private static final int DEFAULT_MIN_THREADS = 4;
|
||||||
private int DEFAULT_MAX_THREADS = 16;
|
private static final int DEFAULT_MAX_THREADS = 16;
|
||||||
|
|
||||||
private static final String MANAGED_EXECUTORS_SERVICE_JNDI_PREFIX = "java:jboss/ee/concurrency/executor/";
|
private static final String MANAGED_EXECUTORS_SERVICE_JNDI_PREFIX = "java:jboss/ee/concurrency/executor/";
|
||||||
|
|
||||||
|
|
|
@ -8,105 +8,88 @@ echo *** Update jgoups subsystem ***
|
||||||
echo *** Update infinispan subsystem ***
|
echo *** Update infinispan subsystem ***
|
||||||
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
|
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
|
||||||
|
|
||||||
|
echo ** Add remote socket binding to infinispan server **
|
||||||
|
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})
|
||||||
|
|
||||||
echo ** Update replicated-cache work element **
|
echo ** Update replicated-cache work element **
|
||||||
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=custom:add( \
|
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=remote:add( \
|
||||||
class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
|
|
||||||
passivation=false, \
|
passivation=false, \
|
||||||
fetch-state=false, \
|
fetch-state=false, \
|
||||||
purge=false, \
|
purge=false, \
|
||||||
preload=false, \
|
preload=false, \
|
||||||
shared=true \
|
shared=true, \
|
||||||
)
|
remote-servers=["remote-cache"], \
|
||||||
|
cache=work, \
|
||||||
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=custom:write-attribute( \
|
properties={ \
|
||||||
name=properties, value={ \
|
|
||||||
rawValues=true, \
|
rawValues=true, \
|
||||||
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, \
|
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
|
||||||
remoteCacheName=work, \
|
|
||||||
sessionCache=false \
|
|
||||||
} \
|
} \
|
||||||
)
|
)
|
||||||
|
|
||||||
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)
|
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)
|
||||||
|
|
||||||
echo ** Update distributed-cache sessions element **
|
echo ** Update distributed-cache sessions element **
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=custom:add( \
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=remote:add( \
|
||||||
class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
|
|
||||||
passivation=false, \
|
passivation=false, \
|
||||||
fetch-state=false, \
|
fetch-state=false, \
|
||||||
purge=false, \
|
purge=false, \
|
||||||
preload=false, \
|
preload=false, \
|
||||||
shared=true \
|
shared=true, \
|
||||||
)
|
remote-servers=["remote-cache"], \
|
||||||
|
cache=sessions, \
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=custom:write-attribute( \
|
properties={ \
|
||||||
name=properties, value={ \
|
rawValues=true, \
|
||||||
remoteCacheName=sessions, \
|
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
|
||||||
useConfigTemplateFromCache=work, \
|
|
||||||
sessionCache=true \
|
|
||||||
} \
|
} \
|
||||||
)
|
)
|
||||||
|
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)
|
||||||
|
|
||||||
echo ** Update distributed-cache offlineSessions element **
|
echo ** Update distributed-cache offlineSessions element **
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=custom:add( \
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=remote:add( \
|
||||||
class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
|
|
||||||
passivation=false, \
|
passivation=false, \
|
||||||
fetch-state=false, \
|
fetch-state=false, \
|
||||||
purge=false, \
|
purge=false, \
|
||||||
preload=false, \
|
preload=false, \
|
||||||
shared=true \
|
shared=true, \
|
||||||
)
|
remote-servers=["remote-cache"], \
|
||||||
|
cache=offlineSessions, \
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=custom:write-attribute( \
|
properties={ \
|
||||||
name=properties, value={ \
|
rawValues=true, \
|
||||||
remoteCacheName=offlineSessions, \
|
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
|
||||||
useConfigTemplateFromCache=work, \
|
|
||||||
sessionCache=true \
|
|
||||||
} \
|
} \
|
||||||
)
|
)
|
||||||
|
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
|
||||||
|
|
||||||
echo ** Update distributed-cache loginFailures element **
|
echo ** Update distributed-cache loginFailures element **
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=custom:add( \
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add( \
|
||||||
class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
|
|
||||||
passivation=false, \
|
passivation=false, \
|
||||||
fetch-state=false, \
|
fetch-state=false, \
|
||||||
purge=false, \
|
purge=false, \
|
||||||
preload=false, \
|
preload=false, \
|
||||||
shared=true \
|
shared=true, \
|
||||||
)
|
remote-servers=["remote-cache"], \
|
||||||
|
cache=loginFailures, \
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=custom:write-attribute( \
|
properties={ \
|
||||||
name=properties, value={ \
|
rawValues=true, \
|
||||||
remoteCacheName=loginFailures, \
|
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
|
||||||
useConfigTemplateFromCache=work, \
|
|
||||||
sessionCache=true \
|
|
||||||
} \
|
} \
|
||||||
)
|
)
|
||||||
|
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)
|
||||||
|
|
||||||
echo ** Update distributed-cache actionTokens element **
|
echo ** Update distributed-cache actionTokens element **
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=custom:add( \
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=remote:add( \
|
||||||
class=org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder, \
|
|
||||||
passivation=false, \
|
passivation=false, \
|
||||||
fetch-state=false, \
|
fetch-state=false, \
|
||||||
purge=false, \
|
purge=false, \
|
||||||
preload=true, \
|
preload=false, \
|
||||||
shared=true \
|
shared=true, \
|
||||||
)
|
cache=actionTokens, \
|
||||||
|
remote-servers=["remote-cache"], \
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=custom:write-attribute( \
|
properties={ \
|
||||||
name=properties, value={ \
|
rawValues=true, \
|
||||||
remoteCacheName=actionTokens, \
|
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
|
||||||
useConfigTemplateFromCache=work, \
|
|
||||||
sessionCache=false \
|
|
||||||
} \
|
} \
|
||||||
)
|
)
|
||||||
|
|
||||||
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)
|
||||||
|
|
||||||
echo ** Update distributed-cache authenticationSessions element **
|
echo ** Update distributed-cache authenticationSessions element **
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.infinispan.remoting.transport.Transport;
|
||||||
import org.jgroups.JChannel;
|
import org.jgroups.JChannel;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
import org.keycloak.testsuite.rest.representation.JGroupsStats;
|
import org.keycloak.testsuite.rest.representation.JGroupsStats;
|
||||||
|
@ -136,11 +137,11 @@ public class TestCacheResource {
|
||||||
if (remoteCache == null) {
|
if (remoteCache == null) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
UserSessionEntity userSession = (UserSessionEntity) remoteCache.get(userSessionId);
|
SessionEntityWrapper<UserSessionEntity> userSession = (SessionEntityWrapper<UserSessionEntity>) remoteCache.get(userSessionId);
|
||||||
if (userSession == null) {
|
if (userSession == null) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return userSession.getLastSessionRefresh();
|
return userSession.getEntity().getLastSessionRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,10 +222,10 @@ public class BruteForceCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
|
|
||||||
log.infof("%s: dc0User1=%d, dc0user2=%d, dc1user1=%d, dc1user2=%d, dc0CacheSize=%d, dc1CacheSize=%d", prefixMessage, dc0user1, dc0user2, dc1user1, dc1user2, dc0CacheSize, dc1CacheSize);
|
log.infof("%s: dc0User1=%d, dc0user2=%d, dc1user1=%d, dc1user2=%d, dc0CacheSize=%d, dc1CacheSize=%d", prefixMessage, dc0user1, dc0user2, dc1user1, dc1user2, dc0CacheSize, dc1CacheSize);
|
||||||
|
|
||||||
Assert.assertEquals(dc0user1, expectedUser1);
|
Assert.assertEquals(expectedUser1, dc0user1);
|
||||||
Assert.assertEquals(dc0user2, expectedUser2);
|
Assert.assertEquals(expectedUser2, dc0user2);
|
||||||
Assert.assertEquals(dc1user1, expectedUser1);
|
Assert.assertEquals(expectedUser1, dc1user1);
|
||||||
Assert.assertEquals(dc1user2, expectedUser2);
|
Assert.assertEquals(expectedUser2, dc1user2);
|
||||||
|
|
||||||
Assert.assertEquals(expectedCacheSize, dc0CacheSize);
|
Assert.assertEquals(expectedCacheSize, dc0CacheSize);
|
||||||
Assert.assertEquals(expectedCacheSize, dc1CacheSize);
|
Assert.assertEquals(expectedCacheSize, dc1CacheSize);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStore;
|
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStore;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStoreFactory;
|
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStoreFactory;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.sessions.SessionData;
|
import org.keycloak.models.sessions.infinispan.changes.sessions.SessionData;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.Retry;
|
import org.keycloak.testsuite.Retry;
|
||||||
|
@ -168,7 +169,7 @@ public class LastSessionRefreshUnitTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Cache<String, SessionEntityWrapper> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
return factory.createAndInit(session, cache, timerIntervalMs, maxIntervalBetweenMessagesSeconds, 10, false);
|
return factory.createAndInit(session, cache, timerIntervalMs, maxIntervalBetweenMessagesSeconds, 10, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue