fix cache

This commit is contained in:
Bill Burke 2016-02-11 22:01:54 -05:00
parent 3bd6974ccb
commit d79a7146ba
35 changed files with 1390 additions and 1997 deletions

View file

@ -1,401 +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.cache.infinispan.counter;
import org.jboss.logging.Logger;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.entities.CachedRole;
import org.keycloak.models.cache.infinispan.ClientAdapter;
import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
import org.keycloak.models.cache.infinispan.GroupAdapter;
import org.keycloak.models.cache.infinispan.RealmAdapter;
import org.keycloak.models.cache.infinispan.RoleAdapter;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCacheRealmProvider implements CacheRealmProvider {
protected static final Logger logger = Logger.getLogger(RevisionedCacheRealmProvider.class);
protected RevisionedRealmCache cache;
protected KeycloakSession session;
protected RealmProvider delegate;
protected boolean transactionActive;
protected boolean setRollbackOnly;
protected Set<String> realmInvalidations = new HashSet<>();
protected Set<String> appInvalidations = new HashSet<>();
protected Set<String> clientTemplateInvalidations = new HashSet<>();
protected Set<String> roleInvalidations = new HashSet<>();
protected Set<String> groupInvalidations = new HashSet<>();
protected Map<String, RealmModel> managedRealms = new HashMap<>();
protected Map<String, ClientModel> managedApplications = new HashMap<>();
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
protected Map<String, RoleModel> managedRoles = new HashMap<>();
protected Map<String, GroupModel> managedGroups = new HashMap<>();
protected boolean clearAll;
public RevisionedCacheRealmProvider(RevisionedRealmCache cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
session.getTransaction().enlistAfterCompletion(getTransaction());
}
@Override
public void clear() {
cache.clear();
}
@Override
public MigrationModel getMigrationModel() {
return getDelegate().getMigrationModel();
}
@Override
public RealmProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
delegate = session.getProvider(RealmProvider.class);
return delegate;
}
@Override
public void registerRealmInvalidation(String id) {
realmInvalidations.add(id);
}
@Override
public void registerApplicationInvalidation(String id) {
appInvalidations.add(id);
}
@Override
public void registerClientTemplateInvalidation(String id) {
clientTemplateInvalidations.add(id);
}
@Override
public void registerRoleInvalidation(String id) {
roleInvalidations.add(id);
}
@Override
public void registerGroupInvalidation(String id) {
groupInvalidations.add(id);
}
protected void runInvalidations() {
for (String id : realmInvalidations) {
cache.invalidateCachedRealmById(id);
}
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
}
for (String id : groupInvalidations) {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
cache.invalidateCachedApplicationById(id);
}
for (String id : clientTemplateInvalidations) {
cache.invalidateCachedClientTemplateById(id);
}
}
private KeycloakTransaction getTransaction() {
return new KeycloakTransaction() {
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
if (delegate == null) return;
if (clearAll) {
cache.clear();
}
runInvalidations();
transactionActive = false;
}
@Override
public void rollback() {
setRollbackOnly = true;
runInvalidations();
transactionActive = false;
}
@Override
public void setRollbackOnly() {
setRollbackOnly = true;
}
@Override
public boolean getRollbackOnly() {
return setRollbackOnly;
}
@Override
public boolean isActive() {
return transactionActive;
}
};
}
@Override
public RealmModel createRealm(String name) {
RealmModel realm = getDelegate().createRealm(name);
registerRealmInvalidation(realm.getId());
return realm;
}
@Override
public RealmModel createRealm(String id, String name) {
RealmModel realm = getDelegate().createRealm(id, name);
registerRealmInvalidation(realm.getId());
return realm;
}
@Override
public RealmModel getRealm(String id) {
CachedRealm cached = cache.getCachedRealm(id);
if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getName());
}
if (cached == null) {
Long loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealm(id);
if (model == null) return null;
if (realmInvalidations.contains(id)) return model;
cached = new RevisionedCachedRealm(loaded, cache, this, model);
logger.tracev("try caching realm: {0} {1}", cached.getName(), loaded);
cache.addCachedRealm(cached);
} else if (realmInvalidations.contains(id)) {
return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) {
return managedRealms.get(id);
}
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(id, adapter);
return adapter;
}
@Override
public RealmModel getRealmByName(String name) {
CachedRealm cached = cache.getCachedRealmByName(name);
if (cached != null) {
logger.tracev("by name cache hit: {0}", cached.getName());
}
if (cached == null) {
Long loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
cached = new RevisionedCachedRealm(loaded, cache, this, model);
logger.tracev("try caching realm: {0}", cached.getName());
cache.addCachedRealm(cached);
} else if (realmInvalidations.contains(cached.getId())) {
return getDelegate().getRealmByName(name);
} else if (managedRealms.containsKey(cached.getId())) {
return managedRealms.get(cached.getId());
}
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(cached.getId(), adapter);
return adapter;
}
@Override
public List<RealmModel> getRealms() {
// Retrieve realms from backend
List<RealmModel> backendRealms = getDelegate().getRealms();
// Return cache delegates to ensure cache invalidated during write operations
List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
for (RealmModel realm : backendRealms) {
RealmModel cached = getRealm(realm.getId());
cachedRealms.add(cached);
}
return cachedRealms;
}
@Override
public boolean removeRealm(String id) {
cache.invalidateCachedRealmById(id);
RealmModel realm = getDelegate().getRealm(id);
Set<RoleModel> realmRoles = null;
if (realm != null) {
realmRoles = realm.getRoles();
}
boolean didIt = getDelegate().removeRealm(id);
realmInvalidations.add(id);
// TODO: Temporary workaround to invalidate cached realm roles
if (didIt && realmRoles != null) {
for (RoleModel role : realmRoles) {
roleInvalidations.add(role.getId());
}
}
return didIt;
}
@Override
public void close() {
if (delegate != null) delegate.close();
}
@Override
public RoleModel getRoleById(String id, RealmModel realm) {
CachedRole cached = cache.getRole(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
Long loaded = UpdateCounter.current();
RoleModel model = getDelegate().getRoleById(id, realm);
if (model == null) return null;
if (roleInvalidations.contains(id)) return model;
if (model.getContainer() instanceof ClientModel) {
cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
} else {
cached = new RevisionedCachedRealmRole(loaded, model, realm);
}
cache.addCachedRole(cached);
} else if (roleInvalidations.contains(id)) {
return getDelegate().getRoleById(id, realm);
} else if (managedRoles.containsKey(id)) {
return managedRoles.get(id);
}
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
managedRoles.put(id, adapter);
return adapter;
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
CachedGroup cached = cache.getGroup(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
Long loaded = UpdateCounter.current();
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new RevisionedCachedGroup(loaded, realm, model);
cache.addCachedGroup(cached);
} else if (groupInvalidations.contains(id)) {
return getDelegate().getGroupById(id, realm);
} else if (managedGroups.containsKey(id)) {
return managedGroups.get(id);
}
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
managedGroups.put(id, adapter);
return adapter;
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
CachedClient cached = cache.getApplication(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached != null) {
logger.tracev("client by id cache hit: {0}", cached.getClientId());
}
if (cached == null) {
Long loaded = UpdateCounter.current();
ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
cache.addCachedClient(cached);
} else if (appInvalidations.contains(id)) {
return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {
return managedApplications.get(id);
}
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
managedApplications.put(id, adapter);
return adapter;
}
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
CachedClientTemplate cached = cache.getClientTemplate(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
Long loaded = UpdateCounter.current();
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model;
cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
cache.addCachedClientTemplate(cached);
} else if (clientTemplateInvalidations.contains(id)) {
return getDelegate().getClientTemplateById(id, realm);
} else if (managedClientTemplates.containsKey(id)) {
return managedClientTemplates.get(id);
}
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
managedClientTemplates.put(id, adapter);
return adapter;
}
}

View file

@ -1,161 +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.cache.infinispan.counter;
import org.infinispan.Cache;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CacheRealmProviderFactory;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RevisionedCacheRealmProviderFactory implements CacheRealmProviderFactory {
private static final Logger log = Logger.getLogger(RevisionedCacheRealmProviderFactory.class);
protected volatile RevisionedRealmCache realmCache;
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
return new RevisionedCacheRealmProvider(realmCache, session);
}
private void lazyInit(KeycloakSession session) {
if (realmCache == null) {
synchronized (this) {
if (realmCache == null) {
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(RevisionedConnectionProviderFactory.VERSION_CACHE_NAME);
cache.addListener(new CacheListener());
realmCache = new RevisionedRealmCache(cache, counterCache, realmLookup);
}
}
}
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan-revisioned";
}
@Listener
public class CacheListener {
@CacheEntryCreated
public void created(CacheEntryCreatedEvent<String, Object> event) {
if (!event.isPre()) {
Object object = event.getValue();
if (object != null) {
if (object instanceof CachedRealm) {
CachedRealm realm = (CachedRealm) object;
realmLookup.put(realm.getName(), realm.getId());
log.tracev("Realm added realm={0}", realm.getName());
}
}
}
}
@CacheEntryRemoved
public void removed(CacheEntryRemovedEvent<String, Object> event) {
if (event.isPre()) {
Object object = event.getValue();
if (object != null) {
remove(object);
}
}
}
@CacheEntryInvalidated
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
if (event.isPre()) {
Object object = event.getValue();
if (object != null) {
remove(object);
}
}
}
@CacheEntriesEvicted
public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
for (Object object : event.getEntries().values()) {
remove(object);
}
}
private void remove(Object object) {
if (object instanceof CachedRealm) {
CachedRealm realm = (CachedRealm) object;
realmLookup.remove(realm.getName());
for (String r : realm.getRealmRoles().values()) {
realmCache.evictCachedRoleById(r);
}
for (String c : realm.getClients().values()) {
realmCache.evictCachedApplicationById(c);
}
log.tracev("Realm removed realm={0}", realm.getName());
} else if (object instanceof CachedClient) {
CachedClient client = (CachedClient) object;
for (String r : client.getRoles().values()) {
realmCache.evictCachedRoleById(r);
}
log.tracev("Client removed client={0}", client.getId());
}
}
}
}

View file

@ -1,47 +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.cache.infinispan.counter;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.jboss.logging.Logger;
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RevisionedConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
public static final String VERSION_CACHE_NAME = "realmVersions";
protected static final Logger logger = Logger.getLogger(RevisionedConnectionProviderFactory.class);
@Override
public String getId() {
return "revisioned";
}
protected void initEmbedded() {
super.initEmbedded();
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
Configuration counterCacheConfiguration = counterConfigBuilder.build();
cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
}
}

View file

@ -1,255 +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.cache.infinispan.counter;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RevisionedRealmCache implements RealmCache {
protected static final Logger logger = Logger.getLogger(RevisionedRealmCache.class);
protected final Cache<String, Long> revisions;
protected final Cache<String, Object> cache;
protected final ConcurrentHashMap<String, String> realmLookup;
public RevisionedRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
this.cache = cache;
this.realmLookup = realmLookup;
this.revisions = revisions;
}
public Cache<String, Object> getCache() {
return cache;
}
private <T> T get(String id, Class<T> type) {
Revisioned o = (Revisioned)cache.get(id);
if (o == null) {
return null;
}
Long rev = revisions.get(id);
if (rev == null) {
logger.tracev("get() missing rev");
return null;
}
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
if (rev > oRev) {
logger.tracev("stale rev: {0} o.rev: {1}", rev.longValue(), oRev);
return null;
}
return o != null && type.isInstance(o) ? type.cast(o) : null;
}
protected Object invalidateObject(String id) {
Object removed = cache.remove(id);
revisions.put(id, UpdateCounter.next());
return removed;
}
protected void addRevisioned(String id, Revisioned object) {
Long rev = revisions.get(id);
if (rev == null) {
logger.tracev("rev was null in addRevisioned, adding one");
rev = UpdateCounter.next();
revisions.put(id, rev);
return;
}
cache.putForExternalRead(id, object);
}
@Override
public void clear() {
cache.clear();
}
@Override
public CachedRealm getCachedRealm(String id) {
return get(id, CachedRealm.class);
}
@Override
public void invalidateCachedRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
invalidateObject(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
public void invalidateCachedRealmById(String id) {
CachedRealm cached = (CachedRealm) invalidateObject(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
public void addCachedRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
addRevisioned(realm.getId(), (Revisioned) realm);
realmLookup.put(realm.getName(), realm.getId());
}
@Override
public CachedRealm getCachedRealmByName(String name) {
String id = realmLookup.get(name);
return id != null ? getCachedRealm(id) : null;
}
@Override
public CachedClient getApplication(String id) {
return get(id, CachedClient.class);
}
@Override
public void invalidateApplication(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
invalidateObject(app.getId());
}
@Override
public void addCachedClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedApplicationById(String id) {
CachedClient client = (CachedClient)invalidateObject(id);
if (client != null) logger.tracev("Removing application {0}", client.getClientId());
}
@Override
public void evictCachedApplicationById(String id) {
logger.tracev("Evicting application {0}", id);
cache.evict(id);
}
@Override
public CachedGroup getGroup(String id) {
return get(id, CachedGroup.class);
}
@Override
public void invalidateGroup(CachedGroup role) {
logger.tracev("Removing group {0}", role.getId());
invalidateObject(role.getId());
}
@Override
public void addCachedGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidateObject(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidateObject(id);
}
@Override
public CachedRole getRole(String id) {
return get(id, CachedRole.class);
}
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());
invalidateObject(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidateObject(id);
}
@Override
public void evictCachedRoleById(String id) {
logger.tracev("Evicting role {0}", id);
cache.evict(id);
}
@Override
public void addCachedRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidateObject(id);
}
@Override
public CachedClientTemplate getClientTemplate(String id) {
return get(id, CachedClientTemplate.class);
}
@Override
public void invalidateClientTemplate(CachedClientTemplate app) {
logger.tracev("Removing client template {0}", app.getId());
invalidateObject(app.getId());
}
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
invalidateObject(id);
}
@Override
public void evictCachedClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
cache.evict(id);
}
}

View file

@ -38,12 +38,12 @@ import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
import org.keycloak.models.cache.infinispan.GroupAdapter;
import org.keycloak.models.cache.infinispan.RealmAdapter;
import org.keycloak.models.cache.infinispan.RoleAdapter;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClient;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientRole;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientTemplate;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedGroup;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealm;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealmRole;
import java.util.Collections;
import java.util.HashMap;
@ -277,6 +277,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
}
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealm(id);
if (model == null) return null;
if (realmInvalidations.contains(id)) return model;
@ -299,10 +300,11 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
logger.tracev("by name cache hit: {0}", cached.getName());
}
if (cached == null) {
Long loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
cached = new RevisionedCachedRealm(null, cache, this, model);
cached = new RevisionedCachedRealm(loaded, cache, this, model);
cache.addCachedRealm(cached);
} else if (realmInvalidations.contains(cached.getId())) {
return getDelegate().getRealmByName(name);
@ -365,6 +367,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
RoleModel model = getDelegate().getRoleById(id, realm);
if (model == null) return null;
if (roleInvalidations.contains(id)) return model;
@ -394,6 +397,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
@ -422,6 +426,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
@ -445,6 +450,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model;

View file

@ -25,7 +25,6 @@ import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@ -39,11 +38,6 @@ public class LockingRealmCache implements RealmCache {
protected final Cache<String, Long> revisions;
protected final Cache<String, Object> cache;
final AtomicLong realmCounter = new AtomicLong();
final AtomicLong clientCounter = new AtomicLong();
final AtomicLong clientTemplateCounter = new AtomicLong();
final AtomicLong roleCounter = new AtomicLong();
final AtomicLong groupCounter = new AtomicLong();
protected final ConcurrentHashMap<String, String> realmLookup;
@ -61,8 +55,8 @@ public class LockingRealmCache implements RealmCache {
return revisions;
}
public void startRevisionBatch() {
revisions.startBatch();
public Long getCurrentRevision(String id) {
return revisions.get(id);
}
public void endRevisionBatch() {
@ -91,33 +85,39 @@ public class LockingRealmCache implements RealmCache {
return o != null && type.isInstance(o) ? type.cast(o) : null;
}
protected Object invalidateObject(String id, AtomicLong counter) {
protected Object invalidateObject(String id) {
Object removed = cache.remove(id);
revisions.put(id, counter.incrementAndGet());
revisions.put(id, UpdateCounter.next());
return removed;
}
protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
protected void addRevisioned(String id, Revisioned object) {
//startRevisionBatch();
try {
//revisions.getAdvancedCache().lock(id);
Long rev = revisions.get(id);
if (rev == null) {
rev = counter.incrementAndGet();
rev = UpdateCounter.current();
revisions.put(id, rev);
return;
}
revisions.startBatch();
revisions.getAdvancedCache().lock(id);
if (!revisions.getAdvancedCache().lock(id)) {
logger.trace("Could not obtain version lock");
}
rev = revisions.get(id);
if (rev == null) {
rev = counter.incrementAndGet();
revisions.put(id, rev);
return;
}
if (rev.equals(object.getRevision())) {
cache.putForExternalRead(id, object);
return;
}
if (rev > object.getRevision()) { // revision is ahead, don't cache
return;
}
// revisions cache has a lower value than the object.revision, so update revision and add it to cache
revisions.put(id, object.getRevision());
cache.putForExternalRead(id, object);
} finally {
endRevisionBatch();
}
@ -127,9 +127,6 @@ public class LockingRealmCache implements RealmCache {
public Long getCurrentRevision(String id) {
return revisions.get(id);
}
@Override
public void clear() {
cache.clear();
@ -143,20 +140,20 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateCachedRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
invalidateObject(realm.getId(), realmCounter);
invalidateObject(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
public void invalidateCachedRealmById(String id) {
CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
CachedRealm cached = (CachedRealm) invalidateObject(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
public void addCachedRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
addRevisioned(realm.getId(), (Revisioned) realm);
realmLookup.put(realm.getName(), realm.getId());
}
@ -175,18 +172,18 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateApplication(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
invalidateObject(app.getId(), clientCounter);
invalidateObject(app.getId());
}
@Override
public void addCachedClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app, clientCounter);
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedApplicationById(String id) {
CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
CachedClient client = (CachedClient)invalidateObject(id);
if (client != null) logger.tracev("Removing application {0}", client.getClientId());
}
@ -204,26 +201,26 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateGroup(CachedGroup role) {
logger.tracev("Removing group {0}", role.getId());
invalidateObject(role.getId(), groupCounter);
invalidateObject(role.getId());
}
@Override
public void addCachedGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role, groupCounter);
addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidateObject(id, groupCounter);
invalidateObject(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidateObject(id, groupCounter);
invalidateObject(id);
}
@Override
@ -234,13 +231,13 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());
invalidateObject(role.getId(), roleCounter);
invalidateObject(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidateObject(id, roleCounter);
invalidateObject(id);
}
@Override
@ -252,13 +249,13 @@ public class LockingRealmCache implements RealmCache {
@Override
public void addCachedRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role, roleCounter);
addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidateObject(id, roleCounter);
invalidateObject(id);
}
@Override
@ -269,19 +266,19 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateClientTemplate(CachedClientTemplate app) {
logger.tracev("Removing client template {0}", app.getId());
invalidateObject(app.getId(), clientTemplateCounter);
invalidateObject(app.getId());
}
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
invalidateObject(id, clientTemplateCounter);
invalidateObject(id);
}
@Override

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.counter;
package org.keycloak.models.cache.infinispan.locking;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.counter;
package org.keycloak.models.cache.infinispan.locking;
import java.util.concurrent.atomic.AtomicLong;

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
@ -6,8 +6,7 @@ import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,9 +1,9 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,11 +1,11 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,9 +1,9 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
@ -6,12 +6,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.entities.CachedRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,9 +1,9 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,9 +1,9 @@
package org.keycloak.models.cache.infinispan.counter.entities;
package org.keycloak.models.cache.infinispan.locking.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.entities.CachedUser;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,160 +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.cache.infinispan.skewed;
import org.infinispan.Cache;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CacheRealmProviderFactory;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RepeatableReadWriteSkewCacheRealmProviderFactory implements CacheRealmProviderFactory {
private static final Logger log = Logger.getLogger(RepeatableReadWriteSkewCacheRealmProviderFactory.class);
protected volatile RepeatableReadWriteSkewRealmCache realmCache;
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
return new RepeatableReadWriteSkewRealmCacheProvider(realmCache, session);
}
private void lazyInit(KeycloakSession session) {
if (realmCache == null) {
synchronized (this) {
if (realmCache == null) {
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.addListener(new CacheListener());
realmCache = new RepeatableReadWriteSkewRealmCache(cache, realmLookup);
}
}
}
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan-versioned";
}
@Listener
public class CacheListener {
@CacheEntryCreated
public void created(CacheEntryCreatedEvent<String, Object> event) {
if (!event.isPre()) {
Object object = event.getValue();
if (object != null) {
if (object instanceof CachedRealm) {
CachedRealm realm = (CachedRealm) object;
realmLookup.put(realm.getName(), realm.getId());
log.tracev("Realm added realm={0}", realm.getName());
}
}
}
}
@CacheEntryRemoved
public void removed(CacheEntryRemovedEvent<String, Object> event) {
if (event.isPre()) {
Object object = event.getValue();
if (object != null) {
remove(object);
}
}
}
@CacheEntryInvalidated
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
if (event.isPre()) {
Object object = event.getValue();
if (object != null) {
remove(object);
}
}
}
@CacheEntriesEvicted
public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
for (Object object : event.getEntries().values()) {
remove(object);
}
}
private void remove(Object object) {
if (object instanceof CachedRealm) {
CachedRealm realm = (CachedRealm) object;
realmLookup.remove(realm.getName());
for (String r : realm.getRealmRoles().values()) {
realmCache.evictCachedRoleById(r);
}
for (String c : realm.getClients().values()) {
realmCache.evictCachedApplicationById(c);
}
log.tracev("Realm removed realm={0}", realm.getName());
} else if (object instanceof CachedClient) {
CachedClient client = (CachedClient) object;
for (String r : client.getRoles().values()) {
realmCache.evictCachedRoleById(r);
}
log.tracev("Client removed client={0}", client.getId());
}
}
}
}

View file

@ -1,164 +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.cache.infinispan.skewed;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.VersioningScheme;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.naming.InitialContext;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RepeatableReadWriteSkewConnectionProviderFactory implements InfinispanConnectionProviderFactory {
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewConnectionProviderFactory.class);
private Config.Scope config;
private EmbeddedCacheManager cacheManager;
private boolean containerManaged;
@Override
public InfinispanConnectionProvider create(KeycloakSession session) {
lazyInit();
return new DefaultInfinispanConnectionProvider(cacheManager);
}
@Override
public void close() {
if (cacheManager != null && !containerManaged) {
cacheManager.stop();
}
cacheManager = null;
}
@Override
public String getId() {
return "versioned";
}
@Override
public void init(Config.Scope config) {
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
private void lazyInit() {
if (cacheManager == null) {
synchronized (this) {
if (cacheManager == null) {
String cacheContainer = config.get("cacheContainer");
if (cacheContainer != null) {
initContainerManaged(cacheContainer);
} else {
initEmbedded();
}
}
}
}
}
private void initContainerManaged(String cacheContainerLookup) {
try {
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
containerManaged = true;
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve cache container", e);
}
}
private void initEmbedded() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = config.getBoolean("clustered", false);
boolean async = config.getBoolean("async", true);
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
cacheManager = new DefaultCacheManager(gcb.build());
containerManaged = false;
logger.debug("Started embedded Infinispan cache container");
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
if (clustered) {
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
}
invalidationConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationConfigBuilder.build());
ConfigurationBuilder userConfigBuilder = new ConfigurationBuilder();
if (clustered) {
userConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
}
Configuration userCacheConfiguration = userConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, userCacheConfiguration);
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
if (clustered) {
String sessionsMode = config.get("sessionsMode", "distributed");
if (sessionsMode.equalsIgnoreCase("replicated")) {
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
} else if (sessionsMode.equalsIgnoreCase("distributed")) {
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
} else {
throw new RuntimeException("Invalid value for sessionsMode");
}
sessionConfigBuilder.clustering().hash()
.numOwners(config.getInt("sessionsOwners", 2))
.numSegments(config.getInt("sessionsSegments", 60)).build();
}
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
}
}

View file

@ -1,269 +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.cache.infinispan.skewed;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RepeatableReadWriteSkewRealmCache implements RealmCache {
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCache.class);
protected final Cache<String, Object> cache;
protected final ConcurrentHashMap<String, String> realmLookup;
public RepeatableReadWriteSkewRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
this.cache = cache;
this.realmLookup = realmLookup;
}
public Cache<String, Object> getCache() {
return cache;
}
public void startBatch() {
logger.trace("*** START BATCH ***");
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
cache.getAdvancedCache().getTransactionManager().begin();
}
} catch (NotSupportedException e) {
throw new RuntimeException(e);
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
public void endBatch(boolean commit) {
logger.trace("*** END BATCH ***");
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
if (commit) {
cache.getAdvancedCache().getTransactionManager().commit();
} else {
cache.getAdvancedCache().getTransactionManager().rollback();
}
}
} catch (Exception e) {
//throw new RuntimeException(e);
}
}
@Override
public void clear() {
cache.clear();
}
@Override
public CachedRealm getCachedRealm(String id) {
return get(id, CachedRealm.class);
}
@Override
public void invalidateCachedRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
invalidate(realm.getId());
realmLookup.remove(realm.getName());
}
protected Object invalidate(String id) {
startBatch();
Object rtn = cache.remove(id);
logger.trace("*** END BATCH ***");
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
if (true) {
cache.getAdvancedCache().getTransactionManager().commit();
} else {
cache.getAdvancedCache().getTransactionManager().rollback();
}
}
} catch (Exception e) {
logger.trace("Failed to commit invalidate");
}
return rtn;
}
@Override
public void invalidateCachedRealmById(String id) {
CachedRealm cached = (CachedRealm) invalidate(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
public void addCachedRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
cache.putForExternalRead(realm.getId(), realm);
realmLookup.put(realm.getName(), realm.getId());
}
@Override
public CachedRealm getCachedRealmByName(String name) {
String id = realmLookup.get(name);
return id != null ? getCachedRealm(id) : null;
}
@Override
public CachedClient getApplication(String id) {
return get(id, CachedClient.class);
}
@Override
public void invalidateApplication(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
invalidate(app.getId());
}
@Override
public void addCachedClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
public void invalidateCachedApplicationById(String id) {
logger.tracev("Removing application {0}", id);
invalidate(id);
}
@Override
public void evictCachedApplicationById(String id) {
logger.tracev("Evicting application {0}", id);
cache.evict(id);
}
@Override
public CachedGroup getGroup(String id) {
return get(id, CachedGroup.class);
}
@Override
public void invalidateGroup(CachedGroup role) {
logger.tracev("Removing group {0}", role.getId());
invalidate(role.getId());
}
@Override
public void addCachedGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
@Override
public void invalidateCachedGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidate(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidate(id);
}
@Override
public CachedRole getRole(String id) {
return get(id, CachedRole.class);
}
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());
invalidate(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidate(id);
}
@Override
public void evictCachedRoleById(String id) {
logger.tracev("Evicting role {0}", id);
cache.evict(id);
}
@Override
public void addCachedRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
@Override
public void invalidateCachedRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidate(id);
}
private <T> T get(String id, Class<T> type) {
Object o = cache.get(id);
return o != null && type.isInstance(o) ? type.cast(o) : null;
}
@Override
public CachedClientTemplate getClientTemplate(String id) {
return get(id, CachedClientTemplate.class);
}
@Override
public void invalidateClientTemplate(CachedClientTemplate app) {
logger.tracev("Removing client template {0}", app.getId());
invalidate(app.getId());
}
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
public void invalidateCachedClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
invalidate(id);
}
@Override
public void evictCachedClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
invalidate(id);
}
}

View file

@ -1,472 +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.cache.infinispan.skewed;
import org.jboss.logging.Logger;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.entities.CachedRole;
import org.keycloak.models.cache.infinispan.ClientAdapter;
import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
import org.keycloak.models.cache.infinispan.GroupAdapter;
import org.keycloak.models.cache.infinispan.RealmAdapter;
import org.keycloak.models.cache.infinispan.RoleAdapter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* DO NOT USE THIS!!
*
* Tries unsuccessfully to use Infinispan with REPEATABLE_READ, write-skew-checking
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RepeatableReadWriteSkewRealmCacheProvider implements CacheRealmProvider {
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCacheProvider.class);
protected RepeatableReadWriteSkewRealmCache cache;
protected KeycloakSession session;
protected RealmProvider delegate;
protected boolean transactionActive;
protected boolean setRollbackOnly;
protected Set<String> realmInvalidations = new HashSet<>();
protected Set<String> appInvalidations = new HashSet<>();
protected Set<String> clientTemplateInvalidations = new HashSet<>();
protected Set<String> roleInvalidations = new HashSet<>();
protected Set<String> groupInvalidations = new HashSet<>();
protected Map<String, RealmModel> managedRealms = new HashMap<>();
protected Map<String, ClientModel> managedApplications = new HashMap<>();
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
protected Map<String, RoleModel> managedRoles = new HashMap<>();
protected Map<String, GroupModel> managedGroups = new HashMap<>();
protected boolean clearAll;
public RepeatableReadWriteSkewRealmCacheProvider(RepeatableReadWriteSkewRealmCache cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
session.getTransaction().enlistAfterCompletion(getTransaction());
}
@Override
public void clear() {
cache.clear();
}
@Override
public MigrationModel getMigrationModel() {
return getDelegate().getMigrationModel();
}
@Override
public RealmProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
delegate = session.getProvider(RealmProvider.class);
return delegate;
}
@Override
public void registerRealmInvalidation(String id) {
realmInvalidations.add(id);
}
@Override
public void registerApplicationInvalidation(String id) {
appInvalidations.add(id);
}
@Override
public void registerClientTemplateInvalidation(String id) {
clientTemplateInvalidations.add(id);
}
@Override
public void registerRoleInvalidation(String id) {
roleInvalidations.add(id);
}
@Override
public void registerGroupInvalidation(String id) {
groupInvalidations.add(id);
}
protected void runInvalidations() {
for (String id : realmInvalidations) {
cache.invalidateCachedRealmById(id);
}
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
}
for (String id : groupInvalidations) {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
cache.invalidateCachedApplicationById(id);
}
for (String id : clientTemplateInvalidations) {
cache.invalidateCachedClientTemplateById(id);
}
}
private KeycloakTransaction getTransaction() {
return new KeycloakTransaction() {
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
if (delegate == null) return;
if (clearAll) {
cache.clear();
}
runInvalidations();
transactionActive = false;
}
@Override
public void rollback() {
setRollbackOnly = true;
runInvalidations();
transactionActive = false;
}
@Override
public void setRollbackOnly() {
setRollbackOnly = true;
}
@Override
public boolean getRollbackOnly() {
return setRollbackOnly;
}
@Override
public boolean isActive() {
return transactionActive;
}
};
}
@Override
public RealmModel createRealm(String name) {
RealmModel realm = getDelegate().createRealm(name);
registerRealmInvalidation(realm.getId());
return realm;
}
@Override
public RealmModel createRealm(String id, String name) {
RealmModel realm = getDelegate().createRealm(id, name);
registerRealmInvalidation(realm.getId());
return realm;
}
@Override
public RealmModel getRealm(String id) {
//cache.startBatch();
cache.startBatch();
boolean batchEnded = false;
try {
CachedRealm cached = cache.getCachedRealm(id);
boolean wasNull = cached == null;
if (cached == null) {
RealmModel model = getDelegate().getRealm(id);
if (model == null) return null;
if (realmInvalidations.contains(id)) return model;
cached = new CachedRealm(cache, this, model);
cache.addCachedRealm(cached);
try {
batchEnded = true;
cache.endBatch(true);
logger.trace("returning new cached realm");
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (realmInvalidations.contains(id)) {
return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) {
return managedRealms.get(id);
}
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(id, adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public RealmModel getRealmByName(String name) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedRealm cached = cache.getCachedRealmByName(name);
boolean wasNull = cached == null;
if (cached == null) {
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
cached = new CachedRealm(cache, this, model);
cache.addCachedRealm(cached);
try {
batchEnded = true;
cache.endBatch(true);
logger.trace("returning new cached realm: " + cached.getName());
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (realmInvalidations.contains(cached.getId())) {
return getDelegate().getRealmByName(name);
} else if (managedRealms.containsKey(cached.getId())) {
return managedRealms.get(cached.getId());
}
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(cached.getId(), adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public List<RealmModel> getRealms() {
// Retrieve realms from backend
List<RealmModel> backendRealms = getDelegate().getRealms();
// Return cache delegates to ensure cache invalidated during write operations
List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
for (RealmModel realm : backendRealms) {
RealmModel cached = getRealm(realm.getId());
cachedRealms.add(cached);
}
return cachedRealms;
}
@Override
public boolean removeRealm(String id) {
cache.invalidateCachedRealmById(id);
RealmModel realm = getDelegate().getRealm(id);
Set<RoleModel> realmRoles = null;
if (realm != null) {
realmRoles = realm.getRoles();
}
boolean didIt = getDelegate().removeRealm(id);
realmInvalidations.add(id);
// TODO: Temporary workaround to invalidate cached realm roles
if (didIt && realmRoles != null) {
for (RoleModel role : realmRoles) {
roleInvalidations.add(role.getId());
}
}
return didIt;
}
@Override
public void close() {
if (delegate != null) delegate.close();
}
@Override
public RoleModel getRoleById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedRole cached = cache.getRole(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
RoleModel model = getDelegate().getRoleById(id, realm);
if (model == null) return null;
if (roleInvalidations.contains(id)) return model;
if (model.getContainer() instanceof ClientModel) {
cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
} else {
cached = new CachedRealmRole(model, realm);
}
cache.addCachedRole(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (roleInvalidations.contains(id)) {
return getDelegate().getRoleById(id, realm);
} else if (managedRoles.containsKey(id)) {
return managedRoles.get(id);
}
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
managedRoles.put(id, adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedGroup cached = cache.getGroup(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new CachedGroup(realm, model);
cache.addCachedGroup(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (groupInvalidations.contains(id)) {
return getDelegate().getGroupById(id, realm);
} else if (managedGroups.containsKey(id)) {
return managedGroups.get(id);
}
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
managedGroups.put(id, adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
CachedClient cached = cache.getApplication(id);
try {
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
cached = new CachedClient(cache, getDelegate(), realm, model);
cache.addCachedClient(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (appInvalidations.contains(id)) {
return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {
return managedApplications.get(id);
}
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
managedApplications.put(id, adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedClientTemplate cached = cache.getClientTemplate(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model;
cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
cache.addCachedClientTemplate(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (clientTemplateInvalidations.contains(id)) {
return getDelegate().getClientTemplateById(id, realm);
} else if (managedClientTemplates.containsKey(id)) {
return managedClientTemplates.get(id);
}
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
managedClientTemplates.put(id, adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
}

View file

@ -16,5 +16,4 @@
#
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory

View file

@ -16,5 +16,4 @@
#
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory

View file

@ -46,8 +46,8 @@ public class ConcurrencyTest extends AbstractClientTest {
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
private static final int DEFAULT_THREADS = 1;
private static final int DEFAULT_ITERATIONS = 5;
private static final int DEFAULT_THREADS = 5;
private static final int DEFAULT_ITERATIONS = 20;
// If enabled only one request is allowed at the time. Useful for checking that test is working.
private static final boolean SYNCHRONIZED = false;

View file

@ -57,6 +57,7 @@
<module>tomcat8</module>
<module>jetty</module>
<module>performance</module>
<module>stress</module>
<module>integration-arquillian</module>
</modules>

571
testsuite/stress/pom.xml Executable file
View file

@ -0,0 +1,571 @@
<?xml version="1.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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>1.9.0.Final-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-stress-tester</artifactId>
<name>Keycloak Stress TestSuite</name>
<description />
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-dependencies-server-all</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-adduser</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-ldap-federation</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-undertow-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>federation-properties-example</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<scope>provided</scope>
</dependency>
<!-- Apache DS -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-util-embedded-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-undertow</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-testsuite-integration</artifactId>
<type>test-jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<inherited>true</inherited>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>keycloak-server</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>org.keycloak.testsuite.KeycloakServer</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mail-server</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>org.keycloak.testsuite.MailServer</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>totp</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>org.keycloak.testsuite.TotpGenerator</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>ldap</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>kerberos</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jpa</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<keycloak.realm.provider>jpa</keycloak.realm.provider>
<keycloak.user.provider>jpa</keycloak.user.provider>
<keycloak.userSessionPersister.provider>jpa</keycloak.userSessionPersister.provider>
<keycloak.eventsStore.provider>jpa</keycloak.eventsStore.provider>
<keycloak.liquibase.logging.level>debug</keycloak.liquibase.logging.level>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mongo</id>
<properties>
<keycloak.connectionsMongo.host>localhost</keycloak.connectionsMongo.host>
<keycloak.connectionsMongo.port>27018</keycloak.connectionsMongo.port>
<keycloak.connectionsMongo.db>keycloak</keycloak.connectionsMongo.db>
<keycloak.connectionsMongo.bindIp>127.0.0.1</keycloak.connectionsMongo.bindIp>
</properties>
<build>
<plugins>
<!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<keycloak.realm.provider>mongo</keycloak.realm.provider>
<keycloak.user.provider>mongo</keycloak.user.provider>
<keycloak.userSessionPersister.provider>mongo</keycloak.userSessionPersister.provider>
<keycloak.eventsStore.provider>mongo</keycloak.eventsStore.provider>
<keycloak.connectionsMongo.host>${keycloak.connectionsMongo.host}</keycloak.connectionsMongo.host>
<keycloak.connectionsMongo.port>${keycloak.connectionsMongo.port}</keycloak.connectionsMongo.port>
<keycloak.connectionsMongo.db>${keycloak.connectionsMongo.db}</keycloak.connectionsMongo.db>
<keycloak.connectionsMongo.bindIp>${keycloak.connectionsMongo.bindIp}</keycloak.connectionsMongo.bindIp>
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<id>default-test</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
<!-- Embedded mongo -->
<plugin>
<groupId>com.github.joelittlejohn.embedmongo</groupId>
<artifactId>embedmongo-maven-plugin</artifactId>
<executions>
<execution>
<id>start-mongodb</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<port>${keycloak.connectionsMongo.port}</port>
<logging>file</logging>
<logFile>${project.build.directory}/mongodb.log</logFile>
<bindIp>${keycloak.connectionsMongo.bindIp}</bindIp>
</configuration>
</execution>
<execution>
<id>stop-mongodb</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- MySQL -->
<profile>
<activation>
<property>
<name>keycloak.connectionsJpa.driver</name>
<value>com.mysql.jdbc.Driver</value>
</property>
</activation>
<id>mysql</id>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</profile>
<!-- PostgreSQL -->
<profile>
<activation>
<property>
<name>keycloak.connectionsJpa.driver</name>
<value>org.postgresql.Driver</value>
</property>
</activation>
<id>postgresql</id>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>clean-jpa</id>
<build>
<plugins>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<configuration>
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
<url>${keycloak.connectionsJpa.url}</url>
<driver>${keycloak.connectionsJpa.driver}</driver>
<username>${keycloak.connectionsJpa.user}</username>
<password>${keycloak.connectionsJpa.password}</password>
<promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
<databaseClass>${keycloak.connectionsJpa.liquibaseDatabaseClass}</databaseClass>
</configuration>
<executions>
<execution>
<id>clean-jpa</id>
<phase>clean</phase>
<goals>
<goal>dropAll</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- Ldap profiles -->
<profile>
<activation>
<property>
<name>ldap.vendor</name>
<value>msad</value>
</property>
</activation>
<id>msad</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>org/keycloak/testsuite/federation/ldap/base/**</include>
</includes>
<excludes>
<exclude>**/LDAPMultipleAttributesTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,137 @@
package org.keycloak.test.stress;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Executes all test threads until completion.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MaxRateExecutor {
public static class RateResult {
StressResult result;
int threads;
long time;
public RateResult(StressResult result, int threads, long time) {
this.result = result;
this.threads = threads;
this.time = time;
}
public StressResult getResult() {
return result;
}
public int getThreads() {
return threads;
}
public long getTime() {
return time;
}
}
List<RateResult> allResults = new LinkedList<>();
RateResult fastest = null;
RateResult last = null;
public void best(TestFactory factory, int jobs) {
fastest = last = null;
int threads = 2;
do {
fastest = last;
try {
last = execute(factory, threads, jobs);
allResults.add(last);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
threads++;
} while (fastest == null || fastest.time > last.time);
}
public RateResult getFastest() {
return fastest;
}
public RateResult getLast() {
return last;
}
public RateResult execute(TestFactory factory, int threads, int jobs) throws InterruptedException, ExecutionException {
List<StressTest> tests = new LinkedList<>();
ExecutorService executor = Executors.newFixedThreadPool(threads);
ExecutorCompletionService<StressTest> completionService = new ExecutorCompletionService<>(executor);
StressResult result = new StressResult("num threads:" + threads);
addTest(factory, result, tests, threads + 5);
long start = System.currentTimeMillis();
for (StressTest stressTest : tests) {
completionService.submit(stressTest);
}
for (int i = 0; i < jobs; i++) {
Future<StressTest> future = completionService.take();
StressTest stressTest = future.get();
if (i < jobs - threads - 5) completionService.submit(stressTest);
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
long end = System.currentTimeMillis() - start;
RateResult rate = new RateResult(result, threads, end);
return rate;
}
private void addTest(TestFactory factory, StressResult result, List<StressTest> tests, int num) {
int add = num - tests.size();
for (int i = 0; i < add; i++) {
Test test = factory.create();
test.init();
StressTest stress = new StressTest(result, test, 1);
tests.add(stress);
}
}
public void printResults() {
System.out.println("*******************");
System.out.println("* Best Result *");
System.out.println("*******************");
printResult(fastest);
}
public void printResult(RateResult result) {
System.out.println("Threads: " + result.getThreads());
System.out.println("Total Time: " + result.getTime());
System.out.println("Successes: " + result.getResult().getSuccess());
System.out.println("Iterations: " + result.getResult().getIterations());
System.out.println("Average time: " + result.getResult().getAverageTime());
System.out.println("Rate: " + result.getResult().getRate());
}
public void printSummary() {
for (RateResult result : allResults) {
System.out.println("*******************");
printSummary();
}
}
public void printSummary(RateResult result) {
System.out.println("Threads: " + result.getThreads());
System.out.println("Total Time: " + result.getTime());
}
}

View file

@ -0,0 +1,65 @@
package org.keycloak.test.stress;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Executes all test threads until completion.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class StressExecutor {
protected List<StressTest> tests = new LinkedList<>();
protected List<StressResult> results = new LinkedList<>();
public void addTest(Class<? extends Test> test, int threads, int iterations) {
StressResult result = new StressResult(test.getName());
results.add(result);
for (int i = 0; i < threads; i++) {
try {
Test t = test.newInstance();
t.init();
StressTest stress = new StressTest(result, t, iterations);
tests.add(stress);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
public void addTest(Test test, StressResult result, int iterations) {
tests.add(new StressTest(result, test, iterations));
}
public void addTest(Test test, int iterations) {
StressResult result = new StressResult(test.getClass().getName());
tests.add(new StressTest(result, test, iterations));
}
public long execute() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(tests.size());
Collections.shuffle(tests);
long start = System.currentTimeMillis();
for (StressTest test : tests) {
executor.submit(test);
}
executor.shutdown();
boolean done = executor.awaitTermination(100, TimeUnit.HOURS);
long end = System.currentTimeMillis() - start;
return end;
}
}

View file

@ -0,0 +1,62 @@
package org.keycloak.test.stress;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class StressResult {
ThreadLocal<Long> start = new ThreadLocal<>();
AtomicLong iterations = new AtomicLong();
AtomicLong totalTime = new AtomicLong();
String name;
AtomicInteger success = new AtomicInteger();
public StressResult(String name) {
this.name = name;
}
public void start() {
start.set(System.currentTimeMillis());
}
public void success() {
success.incrementAndGet();
}
public void end() {
long end = System.currentTimeMillis() - start.get();
totalTime.addAndGet(end);
iterations.incrementAndGet();
}
public int getSuccess() {
return success.get();
}
public String getName() {
return name;
}
public long getTotalTime() {
return totalTime.longValue();
}
public long getIterations() {
return iterations.get();
}
public double getAverageTime() {
return (double)(double)totalTime.get() / (double)iterations.get();
}
public double getRate() {
return (double)(double)iterations.get() / (double)totalTime.get();
}
public void clear() {
iterations.set(0);
totalTime.set(0);
success.set(0);
}
}

View file

@ -0,0 +1,37 @@
package org.keycloak.test.stress;
import jdk.nashorn.internal.codegen.CompilerConstants;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class StressTest implements Callable<StressTest> {
protected StressResult result;
protected Callable<Boolean> test;
protected int iterations;
public StressTest(StressResult result, Callable<Boolean> test, int iterations) {
this.result = result;
this.test = test;
this.iterations = iterations;
}
@Override
public StressTest call() throws Exception {
for (int i = 0; i < iterations; i++) {
result.start();
try {
if (test.call()) {
result.success();
}
} catch (Throwable throwable) {
}
result.end();
}
return this;
}
}

View file

@ -0,0 +1,11 @@
package org.keycloak.test.stress;
import java.util.concurrent.Callable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface Test extends Callable<Boolean> {
void init();
}

View file

@ -0,0 +1,9 @@
package org.keycloak.test.stress;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface TestFactory {
Test create();
}

View file

@ -0,0 +1,104 @@
package org.keycloak.test.stress.tests;
import org.junit.Assert;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.test.stress.Test;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import javax.ws.rs.core.UriBuilder;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class LoginLogout implements Test {
public WebRule webRule = new WebRule(this);
protected String securedResourceUrl;
protected List<String> containsInPage = new LinkedList<>();
protected String realm;
protected String authServerUrl;
protected String username;
protected String password;
protected String loginUrl;
protected String logoutUrl;
@WebResource
protected WebDriver driver;
@WebResource
protected LoginPage loginPage;
public LoginLogout securedResourceUrl(String securedResourceUrl) {
this.securedResourceUrl = securedResourceUrl;
return this;
}
public LoginLogout addPageContains(String contains) {
containsInPage.add(contains);
return this;
}
public LoginLogout realm(String realm) {
this.realm = realm;
return this;
}
public LoginLogout authServerUrl(String authServerUrl) {
this.authServerUrl = authServerUrl;
return this;
}
public LoginLogout username(String username) {
this.username = username;
return this;
}
public LoginLogout password(String password) {
this.password = password;
return this;
}
@Override
public void init() {
loginUrl = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(authServerUrl)).build(realm).toString();
logoutUrl = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(authServerUrl))
.queryParam(OAuth2Constants.REDIRECT_URI, securedResourceUrl).build(realm).toString();
try {
webRule.before();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public Boolean call() throws Exception {
driver.navigate().to(securedResourceUrl);
Assert.assertTrue(driver.getCurrentUrl().startsWith(loginUrl));
loginPage.login(username, password);
Assert.assertTrue(driver.getCurrentUrl().startsWith(securedResourceUrl));
String pageSource = driver.getPageSource();
for (String contains : containsInPage) {
Assert.assertTrue(pageSource.contains(contains));
}
// test logout
driver.navigate().to(logoutUrl);
Assert.assertTrue(driver.getCurrentUrl().startsWith(loginUrl));
return true;
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.test;
import org.junit.Assert;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CustomerDatabaseServlet extends HttpServlet {
private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
Principal principal = req.getUserPrincipal();
Assert.assertNotNull(principal);
pw.printf("<html><head><title>%s</title></head><body>", "Customer Portal");
pw.println("Stian Thorgersen");
pw.println("Bill Burke");
pw.print("</body></html>");
pw.flush();
}
}

View file

@ -0,0 +1,82 @@
package org.keycloak.test;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.test.stress.MaxRateExecutor;
import org.keycloak.test.stress.StressExecutor;
import org.keycloak.test.stress.TestFactory;
import org.keycloak.test.stress.tests.LoginLogout;
import org.keycloak.testsuite.adapter.AdapterTestStrategy;
import org.keycloak.testsuite.adapter.CallAuthenticatedServlet;
import org.keycloak.testsuite.adapter.CustomerDatabaseServlet;
import org.keycloak.testsuite.adapter.CustomerServlet;
import org.keycloak.testsuite.adapter.InputServlet;
import org.keycloak.testsuite.adapter.ProductServlet;
import org.keycloak.testsuite.adapter.SessionServlet;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import java.net.URL;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class FrameworkTest {
@ClassRule
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
createApplicationDeployment()
.name("customer-portal").contextPath("/customer-portal")
.servletClass(org.keycloak.test.CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplication();
}
};
@Test
public void testStressExecutor() throws Exception {
System.out.println("*************************");
System.out.println();
System.out.println();
StressExecutor executor = new StressExecutor();
LoginLogout test = new LoginLogout();
test.authServerUrl("http://localhost:8081/auth")
.realm("demo")
.username("bburke@redhat.com")
.password("password")
.securedResourceUrl("http://localhost:8081/customer-portal");
test.init();
executor.addTest(test, 5);
long time = executor.execute();
System.out.println("Took: " + time );
}
@Test
public void testRate() throws Exception {
System.out.println("*************************");
System.out.println();
System.out.println();
TestFactory factory = new TestFactory() {
@Override
public org.keycloak.test.stress.Test create() {
LoginLogout test = new LoginLogout();
test.authServerUrl("http://localhost:8081/auth")
.realm("demo")
.username("bburke@redhat.com")
.password("password")
.securedResourceUrl("http://localhost:8081/customer-portal");
return test;
}
};
MaxRateExecutor executor = new MaxRateExecutor();
executor.best(factory, 4);
executor.printResults();
}
}

View file

@ -0,0 +1,11 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8081/auth",
"ssl-required" : "external",
"expose-token": true,
"credentials": {
"secret": "password"
}
}

View file

@ -0,0 +1,185 @@
{
"id": "test",
"realm": "test",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"resetPasswordAllowed": true,
"editUsernameAllowed" : true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],
"defaultRoles": [ "user" ],
"smtpServer": {
"from": "auto@keycloak.org",
"host": "localhost",
"port":"3025"
},
"users" : [
{
"username" : "test-user@localhost",
"enabled": true,
"email" : "test-user@localhost",
"firstName": "Tom",
"lastName": "Brady",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user", "offline_access"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "john-doh@localhost",
"enabled": true,
"email" : "john-doh@localhost",
"firstName": "John",
"lastName": "Doh",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "keycloak-user@localhost",
"enabled": true,
"email" : "keycloak-user@localhost",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "topGroupUser",
"enabled": true,
"email" : "top@redhat.com",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"groups": [
"/topGroup"
]
},
{
"username" : "level2GroupUser",
"enabled": true,
"email" : "level2@redhat.com",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"groups": [
"/topGroup/level2group"
]
}
],
"scopeMappings": [
{
"client": "third-party",
"roles": ["user"]
},
{
"client": "test-app",
"roles": ["user"]
}
],
"clients": [
{
"clientId": "test-app",
"enabled": true,
"baseUrl": "http://localhost:8081/app",
"redirectUris": [
"http://localhost:8081/app/*"
],
"adminUrl": "http://localhost:8081/app/logout",
"secret": "password"
},
{
"clientId" : "third-party",
"enabled": true,
"consentRequired": true,
"redirectUris": [
"http://localhost:8081/app/*"
],
"secret": "password"
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "Have User privileges"
},
{
"name": "admin",
"description": "Have Administrator privileges"
}
],
"client" : {
"test-app" : [
{
"name": "customer-user",
"description": "Have Customer User privileges"
},
{
"name": "customer-admin",
"description": "Have Customer Admin privileges"
}
]
}
},
"groups" : [
{
"name": "topGroup",
"attributes": {
"topAttribute": ["true"]
},
"realmRoles": ["user"],
"subGroups": [
{
"name": "level2group",
"realmRoles": ["admin"],
"clientRoles": {
"test-app": ["customer-user"]
},
"attributes": {
"level2Attribute": ["true"]
}
}
]
}
],
"clientScopeMappings": {
"test-app": [
{
"client": "third-party",
"roles": ["customer-user"]
}
]
},
"internationalizationEnabled": true,
"supportedLocales": ["en", "de"],
"defaultLocale": "en"
}