keycloak-uncached

fix cache

2/12/2016 1:01:54 AM

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/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)

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;
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>
 
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"
+    }
+}
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"
+}