keycloak-uncached
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java 401(+0 -401)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java 161(+0 -161)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java 47(+0 -47)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java 255(+0 -255)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClient.java 5(+2 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClientRole.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClientTemplate.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedGroup.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedRealm.java 8(+2 -6)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedRealmRole.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedUser.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java 20(+13 -7)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java 69(+33 -36)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/Revisioned.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/UpdateCounter.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java 160(+0 -160)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java 164(+0 -164)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java 269(+0 -269)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java 472(+0 -472)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory 1(+0 -1)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory 1(+0 -1)
testsuite/pom.xml 1(+1 -0)
testsuite/stress/pom.xml 571(+571 -0)
testsuite/stress/src/test/resources/testrealm.json 185(+185 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
index ea451b0..162a36a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
@@ -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;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
index 9c8e784..2460502 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
@@ -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
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
index ef880ec..299da00 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
@@ -16,5 +16,4 @@
#
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
index 6554765..40ced18 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
@@ -16,5 +16,4 @@
#
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index f510f04..7984fae 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -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;
testsuite/pom.xml 1(+1 -0)
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 6f8079e..bc054ce 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -57,6 +57,7 @@
<module>tomcat8</module>
<module>jetty</module>
<module>performance</module>
+ <module>stress</module>
<module>integration-arquillian</module>
</modules>
testsuite/stress/pom.xml 571(+571 -0)
diff --git a/testsuite/stress/pom.xml b/testsuite/stress/pom.xml
new file mode 100755
index 0000000..f7ea824
--- /dev/null
+++ b/testsuite/stress/pom.xml
@@ -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>
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java
new file mode 100755
index 0000000..c1f0a6b
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java
@@ -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());
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java
new file mode 100755
index 0000000..ebf6ef1
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java
@@ -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;
+
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java
new file mode 100755
index 0000000..080c553
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java
@@ -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);
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java
new file mode 100755
index 0000000..363c5e2
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java
@@ -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;
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java
new file mode 100755
index 0000000..04b7233
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java
@@ -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();
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java
new file mode 100755
index 0000000..aee1f7b
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java
@@ -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();
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java
new file mode 100755
index 0000000..859bc45
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java
@@ -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;
+ }
+}
diff --git a/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java b/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java
new file mode 100755
index 0000000..0115ef1
--- /dev/null
+++ b/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java
@@ -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();
+
+
+ }
+}
diff --git a/testsuite/stress/src/test/java/org/keycloak/test/FrameworkTest.java b/testsuite/stress/src/test/java/org/keycloak/test/FrameworkTest.java
new file mode 100755
index 0000000..7668ac3
--- /dev/null
+++ b/testsuite/stress/src/test/java/org/keycloak/test/FrameworkTest.java
@@ -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();
+ }
+
+}
diff --git a/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json b/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json
new file mode 100755
index 0000000..e9ad987
--- /dev/null
+++ b/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json
@@ -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"
+ }
+}
testsuite/stress/src/test/resources/testrealm.json 185(+185 -0)
diff --git a/testsuite/stress/src/test/resources/testrealm.json b/testsuite/stress/src/test/resources/testrealm.json
new file mode 100755
index 0000000..b4718dd
--- /dev/null
+++ b/testsuite/stress/src/test/resources/testrealm.json
@@ -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"
+}