keycloak-memoizeit

Changes

pom.xml 72(+70 -2)

services/pom.xml 4(+2 -2)

testsuite/pom.xml 166(+166 -0)

Details

diff --git a/examples/as7-eap-demo/server/pom.xml b/examples/as7-eap-demo/server/pom.xml
index 7027dc5..f7e619e 100755
--- a/examples/as7-eap-demo/server/pom.xml
+++ b/examples/as7-eap-demo/server/pom.xml
@@ -118,7 +118,10 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <version>1.3.161</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index 6c62007..d5412ce 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -819,6 +819,7 @@ public class RealmAdapter implements RealmModel {
         RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
         query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
         query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
+        query.setParameter(SocialLinkRelationship.REALM, realm.getName());
         List<SocialLinkRelationship> results = query.getResultList();
         if (results.isEmpty()) {
             return null;
@@ -850,6 +851,7 @@ public class RealmAdapter implements RealmModel {
         relationship.setUser(((UserAdapter)user).getUser());
         relationship.setSocialProvider(socialLink.getSocialProvider());
         relationship.setSocialUsername(socialLink.getSocialUsername());
+        relationship.setRealm(realm.getName());
 
         getRelationshipManager().add(relationship);
     }
@@ -860,6 +862,7 @@ public class RealmAdapter implements RealmModel {
         relationship.setUser(((UserAdapter)user).getUser());
         relationship.setSocialProvider(socialLink.getSocialProvider());
         relationship.setSocialUsername(socialLink.getSocialUsername());
+        relationship.setRealm(realm.getName());
 
         getRelationshipManager().remove(relationship);
     }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
index e9be9d4..da8f04f 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java
@@ -3,6 +3,7 @@ package org.keycloak.models.picketlink.relationships;
 import org.picketlink.idm.model.AbstractAttributedType;
 import org.picketlink.idm.model.Attribute;
 import org.picketlink.idm.model.Relationship;
+import org.picketlink.idm.model.annotation.AttributeProperty;
 import org.picketlink.idm.model.sample.User;
 import org.picketlink.idm.query.AttributeParameter;
 import org.picketlink.idm.query.RelationshipQueryParameter;
@@ -21,6 +22,10 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
     public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
     public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
 
+    // realm is needed to allow searching as combination socialUsername+socialProvider may not be unique
+    // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
+    public static final AttributeParameter REALM = new AttributeParameter("realm");
+
     public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() {
 
         @Override
@@ -39,6 +44,7 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
         this.user = user;
     }
 
+    @AttributeProperty
     public String getSocialProvider() {
         return (String)getAttribute("socialProvider").getValue();
     }
@@ -47,6 +53,7 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
         setAttribute(new Attribute<String>("socialProvider", socialProvider));
     }
 
+    @AttributeProperty
     public String getSocialUsername() {
         return (String)getAttribute("socialUsername").getValue();
     }
@@ -54,4 +61,13 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
     public void setSocialUsername(String socialProviderUserId) {
         setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
     }
+
+    @AttributeProperty
+    public String getRealm() {
+        return (String)getAttribute("realm").getValue();
+    }
+
+    public void setRealm(String realm) {
+        setAttribute(new Attribute<String>("realm", realm));
+    }
 }

pom.xml 72(+70 -2)

diff --git a/pom.xml b/pom.xml
index c2a58bc..9ab5c51 100755
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,13 @@
         <resteasy.version>3.0.4.Final</resteasy.version>
         <undertow.version>1.0.0.Beta12</undertow.version>
         <picketlink.version>2.5.0.Beta6</picketlink.version>
+        <mongo.driver.version>2.11.2</mongo.driver.version>
+        <jboss.logging.version>3.1.1.GA</jboss.logging.version>
+        <hibernate.javax.persistence.version>1.0.1.Final</hibernate.javax.persistence.version>
+        <hibernate.entitymanager.version>3.6.6.Final</hibernate.entitymanager.version>
+        <h2.version>1.3.161</h2.version>
+        <dom4j.version>1.6.1</dom4j.version>
+        <slf4j.version>1.6.1</slf4j.version>
     </properties>
 
     <url>http://keycloak.org</url>
@@ -177,7 +184,7 @@
             <dependency>
                 <groupId>org.jboss.logging</groupId>
                 <artifactId>jboss-logging</artifactId>
-                <version>3.1.1.GA</version>
+                <version>${jboss.logging.version}</version>
             </dependency>
             <dependency>
                 <groupId>junit</groupId>
@@ -187,7 +194,17 @@
             <dependency>
                 <groupId>org.hibernate.javax.persistence</groupId>
                 <artifactId>hibernate-jpa-2.0-api</artifactId>
-                <version>1.0.1.Final</version>
+                <version>${hibernate.javax.persistence.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.h2database</groupId>
+                <artifactId>h2</artifactId>
+                <version>${h2.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.hibernate</groupId>
+                <artifactId>hibernate-entitymanager</artifactId>
+                <version>${hibernate.entitymanager.version}</version>
             </dependency>
             <dependency>
                 <groupId>com.google.api-client</groupId>
@@ -259,6 +276,42 @@
                 <artifactId>de.flapdoodle.embed.mongo</artifactId>
                 <version>1.27</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.jmeter</groupId>
+                <artifactId>ApacheJMeter_java</artifactId>
+                <version>2.9</version>
+            </dependency>
+            <dependency>
+                <groupId>dom4j</groupId>
+                <artifactId>dom4j</artifactId>
+                <version>${dom4j.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-api</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+            <!-- Needed for picketlink perf test -->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>5.1.25</version>
+            </dependency>
+
+			<dependency>
+  				<groupId>org.jboss.arquillian</groupId>
+  				<artifactId>arquillian-bom</artifactId>
+  				<version>1.1.1.Final</version>
+  				<type>pom</type>
+  				<scope>import</scope>
+			</dependency>			
+			<dependency>
+				<groupId>org.jboss.arquillian.extension</groupId>
+				<artifactId>arquillian-drone-bom</artifactId>
+				<version>1.2.0.Beta1</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -340,6 +393,21 @@
 						<failOnMissingWebXml>false</failOnMissingWebXml>
 					</configuration>
 				</plugin>
+                <plugin>
+                    <groupId>com.lazerycode.jmeter</groupId>
+                    <artifactId>jmeter-maven-plugin</artifactId>
+                    <version>1.8.1</version>
+                </plugin>
+                <plugin>
+                    <groupId>com.lazerycode.jmeter</groupId>
+                    <artifactId>jmeter-analysis-maven-plugin</artifactId>
+                    <version>1.0.4</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>2.2</version>
+                </plugin>
             </plugins>
         </pluginManagement>
 

services/pom.xml 4(+2 -2)

diff --git a/services/pom.xml b/services/pom.xml
index 8696a5d..b35bef4 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -147,10 +147,12 @@
         <dependency>
             <groupId>org.picketlink</groupId>
             <artifactId>picketlink-common</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.mongodb</groupId>
             <artifactId>mongo-java-driver</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>de.flapdoodle.embed</groupId>
@@ -170,13 +172,11 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <version>1.3.161</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-entitymanager</artifactId>
-            <version>3.6.6.Final</version>
             <scope>test</scope>
         </dependency>
 		<dependency>
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java
index 838a6a4..71cd3a1 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java
@@ -43,6 +43,10 @@ public class NoSQLSession implements KeycloakSession {
 
     @Override
     public RealmModel createRealm(String id, String name) {
+        if (getRealm(id) != null) {
+            throw new IllegalStateException("Realm with id '" + id + "' already exists");
+        }
+
         RealmData newRealm = new RealmData();
         newRealm.setId(id);
         newRealm.setName(name);
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
index 2165165..85c9fcd 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java
@@ -273,6 +273,15 @@ public class RealmAdapter implements RealmModel {
         return new UserAdapter(userData, noSQL);
     }
 
+    // This method doesn't exists on interface actually
+    public void removeUser(String name) {
+        NoSQLQuery query = noSQL.createQueryBuilder()
+                .andCondition("loginName", name)
+                .andCondition("realmId", getOid())
+                .build();
+        noSQL.removeObjects(UserData.class, query);
+    }
+
     @Override
     public RoleAdapter getRole(String name) {
         NoSQLQuery query = noSQL.createQueryBuilder()
@@ -659,6 +668,7 @@ public class RealmAdapter implements RealmModel {
         NoSQLQuery query = noSQL.createQueryBuilder()
                 .andCondition("socialProvider", socialLink.getSocialProvider())
                 .andCondition("socialUsername", socialLink.getSocialUsername())
+                .andCondition("realmId", getOid())
                 .build();
         SocialLinkData socialLinkData = noSQL.loadSingleObject(SocialLinkData.class, query);
 
@@ -696,6 +706,7 @@ public class RealmAdapter implements RealmModel {
         socialLinkData.setSocialProvider(socialLink.getSocialProvider());
         socialLinkData.setSocialUsername(socialLink.getSocialUsername());
         socialLinkData.setUserId(userData.getId());
+        socialLinkData.setRealmId(getOid());
 
         noSQL.saveObject(socialLinkData);
     }
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java
index 7cfe6f5..46b1c26 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java
@@ -15,6 +15,9 @@ public class SocialLinkData extends AbstractNoSQLObject {
     private String socialUsername;
     private String socialProvider;
     private String userId;
+    // realmId is needed to allow searching as combination socialUsername+socialProvider may not be unique
+    // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
+    private String realmId;
 
     @NoSQLField
     public String getSocialUsername() {
@@ -42,4 +45,13 @@ public class SocialLinkData extends AbstractNoSQLObject {
     public void setUserId(String userId) {
         this.userId = userId;
     }
+
+    @NoSQLField
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
 }

testsuite/pom.xml 166(+166 -0)

diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 7c5278d..adb5aa3 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -182,6 +182,10 @@
 			<groupId>org.seleniumhq.selenium</groupId>
 			<artifactId>selenium-java</artifactId>
   		</dependency>
+        <dependency>
+            <groupId>org.apache.jmeter</groupId>
+            <artifactId>ApacheJMeter_java</artifactId>
+        </dependency>
 	</dependencies>
 	<build>
 		<plugins>
@@ -193,6 +197,17 @@
 					<target>1.6</target>
 				</configuration>
 			</plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
 		</plugins>
 	</build>
 
@@ -219,5 +234,156 @@
 				</dependency>
 			</dependencies>
 		</profile>
+
+        <profile>
+            <id>performance-tests</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.lazerycode.jmeter</groupId>
+                        <artifactId>jmeter-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>jmeter-tests</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>jmeter</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.keycloak</groupId>
+                                <artifactId>keycloak-testsuite</artifactId>
+                                <version>${project.version}</version>
+                                <type>test-jar</type>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.keycloak</groupId>
+                                <artifactId>keycloak-services</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.jboss.resteasy</groupId>
+                                <artifactId>jaxrs-api</artifactId>
+                                <version>${resteasy.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.jboss.resteasy</groupId>
+                                <artifactId>resteasy-jaxrs</artifactId>
+                                <version>${resteasy.version}</version>
+                                <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>
+                                    <exclusion>
+                                        <groupId>commons-io</groupId>
+                                        <artifactId>commons-io</artifactId>
+                                    </exclusion>
+                                </exclusions>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.jboss.logging</groupId>
+                                <artifactId>jboss-logging</artifactId>
+                                <version>${jboss.logging.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.picketlink</groupId>
+                                <artifactId>picketlink-idm-impl</artifactId>
+                                <version>${picketlink.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.picketlink</groupId>
+                                <artifactId>picketlink-idm-simple-schema</artifactId>
+                                <version>${picketlink.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.picketlink</groupId>
+                                <artifactId>picketlink-config</artifactId>
+                                <version>${picketlink.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.mongodb</groupId>
+                                <artifactId>mongo-java-driver</artifactId>
+                                <version>${mongo.driver.version}</version>
+                            </dependency>
+
+                            <!-- Needed for picketlink -->
+                            <dependency>
+                                <groupId>org.hibernate.javax.persistence</groupId>
+                                <artifactId>hibernate-jpa-2.0-api</artifactId>
+                                <version>${hibernate.javax.persistence.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>com.h2database</groupId>
+                                <artifactId>h2</artifactId>
+                                <version>${h2.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.hibernate</groupId>
+                                <artifactId>hibernate-entitymanager</artifactId>
+                                <version>${hibernate.entitymanager.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>dom4j</groupId>
+                                <artifactId>dom4j</artifactId>
+                                <version>${dom4j.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.slf4j</groupId>
+                                <artifactId>slf4j-api</artifactId>
+                                <version>${slf4j.version}</version>
+                            </dependency>
+
+                            <!-- Needed just for picketlink perf test -->
+                            <dependency>
+                                <groupId>mysql</groupId>
+                                <artifactId>mysql-connector-java</artifactId>
+                            </dependency>
+
+                        </dependencies>
+                    </plugin>
+
+                    <plugin>
+                        <groupId>com.lazerycode.jmeter</groupId>
+                        <artifactId>jmeter-analysis-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>jmeter-tests-analyze</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>analyze</goal>
+                                </goals>
+                                <configuration>
+                                    <source>${project.build.directory}/jmeter/results/*.jtl</source>
+                                    <targetDirectory>${project.build.directory}/jmeter/results</targetDirectory>
+                                    <preserveDirectories>false</preserveDirectories>
+                                    <writers>
+                                        <com.lazerycode.jmeter.analyzer.writer.SummaryTextToStdOutWriter/>
+                                        <!--<com.lazerycode.jmeter.analyzer.writer.SummaryTextToFileWriter/>-->
+                                        <com.lazerycode.jmeter.analyzer.writer.HtmlWriter/>
+                                        <!--<com.lazerycode.jmeter.analyzer.writer.DetailsToCsvWriter/>-->
+                                        <com.lazerycode.jmeter.analyzer.writer.DetailsToHtmlWriter/>
+                                        <com.lazerycode.jmeter.analyzer.writer.ChartWriter/>
+                                    </writers>
+
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                </plugins>
+            </build>
+        </profile>
+
 	</profiles>
 </project>
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java
new file mode 100644
index 0000000..bc38e6d
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java
@@ -0,0 +1,141 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
+import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
+import org.apache.jmeter.samplers.SampleResult;
+import org.keycloak.services.models.KeycloakSession;
+import org.keycloak.services.models.KeycloakSessionFactory;
+import org.keycloak.services.models.KeycloakTransaction;
+import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession;
+import org.keycloak.services.resources.KeycloakApplication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BaseJMeterPerformanceTest extends AbstractJavaSamplerClient {
+
+
+    private static FutureTask<KeycloakSessionFactory> factoryProvider = new FutureTask<KeycloakSessionFactory>(new Callable() {
+
+        @Override
+        public KeycloakSessionFactory call() throws Exception {
+            return KeycloakApplication.buildSessionFactory();
+        }
+
+    });
+    private static AtomicInteger counter = new AtomicInteger();
+
+    private KeycloakSessionFactory factory;
+    // private KeycloakSession identitySession;
+    private Worker worker;
+    private boolean setupSuccess = false;
+
+
+    // Executed once per JMeter thread
+    @Override
+    public void setupTest(JavaSamplerContext context) {
+        super.setupTest(context);
+
+        worker = getWorker();
+
+        factory = getFactory();
+        KeycloakSession identitySession = factory.createSession();
+        KeycloakTransaction transaction = identitySession.getTransaction();
+        transaction.begin();
+
+        int workerId = counter.getAndIncrement();
+        try {
+            worker.setup(workerId, identitySession);
+            setupSuccess = true;
+        } finally {
+            if (setupSuccess) {
+                transaction.commit();
+            } else {
+                transaction.rollback();
+            }
+            identitySession.close();
+        }
+    }
+
+    private static KeycloakSessionFactory getFactory() {
+        factoryProvider.run();
+        try {
+            return factoryProvider.get();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    private Worker getWorker() {
+        String workerClass = System.getProperty("keycloak.perf.workerClass");
+        if (workerClass == null) {
+            throw new IllegalArgumentException("System property keycloak.perf.workerClass needs to be provided");
+        }
+
+        try {
+            Class workerClazz = Class.forName(workerClass);
+            return (Worker)workerClazz.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    @Override
+    public SampleResult runTest(JavaSamplerContext context) {
+        SampleResult result = new SampleResult();
+        result.sampleStart();
+
+        if (!setupSuccess) {
+            getLogger().error("setupTest didn't executed successfully. Skipping");
+            result.setResponseCode("500");
+            result.sampleEnd();
+            result.setSuccessful(true);
+            return result;
+        }
+
+        KeycloakSession identitySession = factory.createSession();
+        KeycloakTransaction transaction = identitySession.getTransaction();
+        try {
+            transaction.begin();
+
+            worker.run(result, identitySession);
+
+            result.setResponseCodeOK();
+            transaction.commit();
+        } catch (Exception e) {
+            getLogger().error("Error during worker processing", e);
+            result.setResponseCode("500");
+            transaction.rollback();
+        } finally {
+            result.sampleEnd();
+            result.setSuccessful(true);
+            identitySession.close();
+        }
+
+        return result;
+    }
+
+
+    // Executed once per JMeter thread
+    @Override
+    public void teardownTest(JavaSamplerContext context) {
+        super.teardownTest(context);
+
+        if (worker != null) {
+            worker.tearDown();
+        }
+
+        // TODO: Assumption is that tearDownTest is executed for each setupTest. Verify if it's always true...
+        if (counter.decrementAndGet() == 0) {
+            if (factory != null) {
+                factory.close();
+            }
+        }
+    }
+}
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java
new file mode 100644
index 0000000..a3b37e0
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java
@@ -0,0 +1,101 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.models.ApplicationModel;
+import org.keycloak.services.models.KeycloakSession;
+import org.keycloak.services.models.RealmModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CreateRealmsWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_REALMS_IN_EACH_REPORT = 100;
+
+    private static AtomicInteger realmCounter = new AtomicInteger(0);
+
+    private int offset;
+    private int appsPerRealm;
+    private int rolesPerRealm;
+    private int defaultRolesPerRealm;
+    private int rolesPerApp;
+    private boolean createRequiredCredentials;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        offset = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.realms.offset", Integer.class);
+        appsPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.appsPerRealm", Integer.class);
+        rolesPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.rolesPerRealm", Integer.class);
+        defaultRolesPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.defaultRolesPerRealm", Integer.class);
+        rolesPerApp = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.rolesPerApp", Integer.class);
+        createRequiredCredentials = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.createRequiredCredentials", Boolean.class);
+
+        realmCounter.compareAndSet(0, offset);
+
+        StringBuilder logBuilder = new StringBuilder("Read setup: ")
+                .append("offset=" + offset)
+                .append(", appsPerRealm=" + appsPerRealm)
+                .append(", rolesPerRealm=" + rolesPerRealm)
+                .append(", defaultRolesPerRealm=" + defaultRolesPerRealm)
+                .append(", rolesPerApp=" + rolesPerApp)
+                .append(", createRequiredCredentials=" + createRequiredCredentials);
+        log.info(logBuilder.toString());
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        int realmNumber = realmCounter.getAndIncrement();
+        String realmName = PerfTestUtils.getRealmName(realmNumber);
+        RealmManager realmManager = new RealmManager(identitySession);
+        RealmModel realm = realmManager.createRealm(realmName, realmName);
+
+        // Add roles
+        for (int i=1 ; i<=rolesPerRealm ; i++) {
+            realm.addRole(PerfTestUtils.getRoleName(realmNumber, i));
+        }
+
+        // Add default roles
+        for (int i=1 ; i<=defaultRolesPerRealm ; i++) {
+            realm.addDefaultRole(PerfTestUtils.getDefaultRoleName(realmNumber, i));
+        }
+
+        // Add applications
+        for (int i=1 ; i<=appsPerRealm ; i++) {
+            ApplicationModel application = realm.addApplication(PerfTestUtils.getApplicationName(realmNumber, i));
+            for (int j=1 ; j<=rolesPerApp ; j++) {
+                application.addRole(PerfTestUtils.getApplicationRoleName(realmNumber, i, j));
+            }
+        }
+
+        // Add required credentials
+        if (createRequiredCredentials) {
+            realmManager.addRequiredCredential(realm, CredentialRepresentation.PASSWORD);
+            realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.PASSWORD);
+            realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.PASSWORD);
+            realmManager.addRequiredCredential(realm, CredentialRepresentation.TOTP);
+            realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.TOTP);
+            realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.TOTP);
+            realmManager.addRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT);
+            realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT);
+            realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT);
+        }
+
+        log.info("Finished creation of realm " + realmName);
+
+        int labelC = ((realmNumber - 1) / NUMBER_OF_REALMS_IN_EACH_REPORT) * NUMBER_OF_REALMS_IN_EACH_REPORT;
+        result.setSampleLabel("CreateRealms " + (labelC + 1) + "-" + (labelC + NUMBER_OF_REALMS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+
+}
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
new file mode 100644
index 0000000..f427944
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
@@ -0,0 +1,120 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.models.KeycloakSession;
+import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.RoleModel;
+import org.keycloak.services.models.SocialLinkModel;
+import org.keycloak.services.models.UserCredentialModel;
+import org.keycloak.services.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CreateUsersWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_USERS_IN_EACH_REPORT = 5000;
+
+    // Total number of users created during whole test
+    private static AtomicInteger totalUserCounter = new AtomicInteger();
+
+    // Adding users will always start from 1. Each worker thread needs to add users to single realm, which is dedicated just for this worker
+    private int userCounterInRealm = 0;
+    private String realmId;
+
+    private int realmsOffset;
+    private boolean addBasicUserAttributes;
+    private boolean addDefaultRoles;
+    private boolean addPassword;
+    private int socialLinksPerUserCount;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.realms.offset", Integer.class);
+        addBasicUserAttributes = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addBasicUserAttributes", Boolean.class);
+        addDefaultRoles = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addDefaultRoles", Boolean.class);
+        addPassword = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addPassword", Boolean.class);
+        socialLinksPerUserCount = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.socialLinksPerUserCount", Integer.class);
+
+        int realmNumber = realmsOffset + workerId;
+        realmId = PerfTestUtils.getRealmName(realmNumber);
+
+        StringBuilder logBuilder = new StringBuilder("Read setup: ")
+                .append("realmsOffset=" + realmsOffset)
+                .append(", addBasicUserAttributes=" + addBasicUserAttributes)
+                .append(", addDefaultRoles=" + addDefaultRoles)
+                .append(", addPassword=" + addPassword)
+                .append(", socialLinksPerUserCount=" + socialLinksPerUserCount)
+                .append(", realmId=" + realmId);
+        log.info(logBuilder.toString());
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        // We need to obtain realm first
+        RealmModel realm = identitySession.getRealm(realmId);
+        if (realm == null) {
+            throw new IllegalStateException("Realm '" + realmId + "' not found");
+        }
+
+        int userNumber = ++userCounterInRealm;
+        int totalUserNumber = totalUserCounter.incrementAndGet();
+
+        String username = PerfTestUtils.getUsername(userNumber);
+
+        UserModel user = realm.addUser(username);
+
+        // Add basic user attributes (NOTE: Actually backend is automatically upgraded during each setter call)
+        if (addBasicUserAttributes) {
+            user.setFirstName(username + "FN");
+            user.setLastName(username + "LN");
+            user.setEmail(username + "@email.com");
+        }
+
+        // Adding default roles of realm to user
+        if (addDefaultRoles) {
+            for (RoleModel role : realm.getDefaultRoles()) {
+                realm.grantRole(user, role);
+            }
+        }
+
+        // Creating password (will be same as username)
+        if (addPassword) {
+            UserCredentialModel password = new UserCredentialModel();
+            password.setType(CredentialRepresentation.PASSWORD);
+            password.setValue(username);
+            realm.updateCredential(user, password);
+        }
+
+        // Creating some socialLinks
+        for (int i=0 ; i<socialLinksPerUserCount ; i++) {
+            String socialProvider;
+            switch (i) {
+                case 0: socialProvider = "facebook"; break;
+                case 1: socialProvider = "twitter"; break;
+                case 2: socialProvider = "google"; break;
+                default: throw new IllegalArgumentException("Total number of socialLinksPerUserCount is " + socialLinksPerUserCount
+                        + " which is too big.");
+            }
+
+            SocialLinkModel socialLink = new SocialLinkModel(socialProvider, username);
+            realm.addSocialLink(user, socialLink);
+        }
+
+        log.info("Finished creation of user " + username + " in realm: " + realm.getId());
+
+        int labelC = ((totalUserNumber - 1) / NUMBER_OF_USERS_IN_EACH_REPORT) * NUMBER_OF_USERS_IN_EACH_REPORT;
+        result.setSampleLabel("CreateUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_USERS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+}
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java
new file mode 100644
index 0000000..75b3554
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java
@@ -0,0 +1,46 @@
+package org.keycloak.testsuite.performance;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PerfTestUtils {
+
+    public static <T> T readSystemProperty(String propertyName, Class<T> expectedClass) {
+        String propAsString = System.getProperty(propertyName);
+        if (propAsString == null || propAsString.length() == 0) {
+            throw new IllegalArgumentException("Property '" + propertyName + "' not specified");
+        }
+
+        if (Integer.class.equals(expectedClass)) {
+            return expectedClass.cast(Integer.parseInt(propAsString));
+        } else if (Boolean.class.equals(expectedClass)) {
+            return expectedClass.cast(Boolean.valueOf(propAsString));
+        }  else {
+            throw new IllegalArgumentException("Not supported type " + expectedClass);
+        }
+    }
+
+    public static String getRealmName(int realmNumber) {
+        return "realm" + realmNumber;
+    }
+
+    public static String getApplicationName(int realmNumber, int applicationNumber) {
+        return getRealmName(realmNumber) + "application" + applicationNumber;
+    }
+
+    public static String getRoleName(int realmNumber, int roleNumber) {
+        return getRealmName(realmNumber) + "role" + roleNumber;
+    }
+
+    public static String getDefaultRoleName(int realmNumber, int defaultRoleNumber) {
+        return getRealmName(realmNumber) + "defrole" + defaultRoleNumber;
+    }
+
+    public static String getApplicationRoleName(int realmNumber, int applicationNumber, int roleNumber) {
+        return getApplicationName(realmNumber, applicationNumber) + "role" + roleNumber;
+    }
+
+    public static String getUsername(int userNumber) {
+        return "user" + userNumber;
+    }
+}
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
new file mode 100644
index 0000000..1439919
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java
@@ -0,0 +1,127 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.services.models.KeycloakSession;
+import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.SocialLinkModel;
+import org.keycloak.services.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ReadUsersWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_ITERATIONS_IN_EACH_REPORT = 5000;
+
+    // Total number of iterations read during whole test
+    private static AtomicInteger totalIterationCounter = new AtomicInteger();
+
+    // Reading users will always start from 1. Each worker thread needs to read users to single realm, which is dedicated just for this worker
+    private int userCounterInRealm = 0;
+
+    private int realmsOffset;
+    private int readUsersPerIteration;
+    private int countOfUsersPerRealm;
+    private boolean readRoles;
+    private boolean readScopes;
+    private boolean readPassword;
+    private boolean readSocialLinks;
+    private boolean searchBySocialLinks;
+
+    private String realmId;
+    private int iterationNumber;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.realms.offset", Integer.class);
+        readUsersPerIteration = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readUsersPerIteration", Integer.class);
+        countOfUsersPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.countOfUsersPerRealm", Integer.class);
+        readRoles = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readRoles", Boolean.class);
+        readScopes = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readScopes", Boolean.class);
+        readPassword = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readPassword", Boolean.class);
+        readSocialLinks = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readSocialLinks", Boolean.class);
+        searchBySocialLinks = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.searchBySocialLinks", Boolean.class);
+
+        int realmNumber = realmsOffset + workerId;
+        realmId = PerfTestUtils.getRealmName(realmNumber);
+
+        StringBuilder logBuilder = new StringBuilder("Read setup: ")
+                .append("realmsOffset=" + realmsOffset)
+                .append(", readUsersPerIteration=" + readUsersPerIteration)
+                .append(", countOfUsersPerRealm=" + countOfUsersPerRealm)
+                .append(", readRoles=" + readRoles)
+                .append(", readScopes=" + readScopes)
+                .append(", readPassword=" + readPassword)
+                .append(", readSocialLinks=" + readSocialLinks)
+                .append(", searchBySocialLinks=" + searchBySocialLinks)
+                .append(", realmId=" + realmId);
+        log.info(logBuilder.toString());
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        // We need to obtain realm first
+        RealmModel realm = identitySession.getRealm(realmId);
+        if (realm == null) {
+            throw new IllegalStateException("Realm '" + realmId + "' not found");
+        }
+
+        int totalIterationNumber = totalIterationCounter.incrementAndGet();
+        String lastUsername = null;
+
+        for (int i=0 ; i<readUsersPerIteration ; i++) {
+            ++userCounterInRealm;
+
+            // Start reading users from 1
+            if (userCounterInRealm > countOfUsersPerRealm) {
+                userCounterInRealm = 1;
+            }
+
+            String username = PerfTestUtils.getUsername(userCounterInRealm);
+            lastUsername = username;
+
+            UserModel user = realm.getUser(username);
+
+            // Read roles of user in realm
+            if (readRoles) {
+                realm.getRoleMappings(user);
+            }
+
+            // Read scopes of user in realm
+            if (readScopes) {
+                realm.getScope(user);
+            }
+
+            // Validate password (shoould be same as username)
+            if (readPassword) {
+                realm.validatePassword(user, username);
+            }
+
+            // Read socialLinks of user
+            if (readSocialLinks) {
+                realm.getSocialLinks(user);
+            }
+
+            // Try to search by social links
+            if (searchBySocialLinks) {
+                SocialLinkModel socialLink = new SocialLinkModel("facebook", username);
+                realm.getUserBySocialLink(socialLink);
+            }
+        }
+
+        log.info("Finished iteration " + ++iterationNumber + " in ReadUsers test for " + realmId + " worker. Last read user " + lastUsername  + " in realm: " + realmId);
+
+        int labelC = ((totalIterationNumber - 1) / NUMBER_OF_ITERATIONS_IN_EACH_REPORT) * NUMBER_OF_ITERATIONS_IN_EACH_REPORT;
+        result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_ITERATIONS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+}
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java
new file mode 100644
index 0000000..966024a
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java
@@ -0,0 +1,72 @@
+package org.keycloak.testsuite.performance;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+import org.keycloak.services.models.KeycloakSession;
+import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.SocialLinkModel;
+import org.keycloak.services.models.UserModel;
+import org.keycloak.services.models.nosql.keycloak.adapters.RealmAdapter;
+import org.keycloak.services.resources.KeycloakApplication;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RemoveUsersWorker implements Worker {
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final int NUMBER_OF_USERS_IN_EACH_REPORT = 5000;
+
+    // Total number of users removed during whole test
+    private static AtomicInteger totalUserCounter = new AtomicInteger();
+
+    // Removing users will always start from 1. Each worker thread needs to add users to single realm, which is dedicated just for this worker
+    private int userCounterInRealm = 0;
+    private RealmModel realm;
+
+    private int realmsOffset;
+
+    @Override
+    public void setup(int workerId, KeycloakSession identitySession) {
+        realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.removeUsers.realms.offset", Integer.class);
+
+        int realmNumber = realmsOffset + workerId;
+        String realmId = PerfTestUtils.getRealmName(realmNumber);
+        realm = identitySession.getRealm(realmId);
+        if (realm == null) {
+            throw new IllegalStateException("Realm '" + realmId + "' not found");
+        }
+
+        log.info("Read setup: realmsOffset=" + realmsOffset);
+    }
+
+    @Override
+    public void run(SampleResult result, KeycloakSession identitySession) {
+        int userNumber = ++userCounterInRealm;
+        int totalUserNumber = totalUserCounter.incrementAndGet();
+
+        String username = PerfTestUtils.getUsername(userNumber);
+
+        // TODO: Not supported in model actually. We support operation just in MongoDB
+        // UserModel user = realm.removeUser(username);
+        if (KeycloakApplication.SESSION_FACTORY_MONGO.equals(System.getProperty(KeycloakApplication.SESSION_FACTORY))) {
+            RealmAdapter mongoRealm = (RealmAdapter)realm;
+            mongoRealm.removeUser(username);
+        } else {
+            throw new IllegalArgumentException("Actually removing of users is supported just for MongoDB");
+        }
+
+        log.info("Finished removing of user " + username + " in realm: " + realm.getId());
+
+        int labelC = ((totalUserNumber - 1) / NUMBER_OF_USERS_IN_EACH_REPORT) * NUMBER_OF_USERS_IN_EACH_REPORT;
+        result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_USERS_IN_EACH_REPORT));
+    }
+
+    @Override
+    public void tearDown() {
+    }
+}
diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java
new file mode 100644
index 0000000..6060f49
--- /dev/null
+++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java
@@ -0,0 +1,19 @@
+package org.keycloak.testsuite.performance;
+
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.log.Logger;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface Worker {
+
+    void setup(int workerId, KeycloakSession identitySession);
+
+    void run(SampleResult result, KeycloakSession identitySession);
+
+    void tearDown();
+
+}
diff --git a/testsuite/src/test/jmeter/jmeter.properties b/testsuite/src/test/jmeter/jmeter.properties
new file mode 100644
index 0000000..39b6ce4
--- /dev/null
+++ b/testsuite/src/test/jmeter/jmeter.properties
@@ -0,0 +1,20 @@
+#Thu Mar 07 18:46:04 BRT 2013
+not_in_menu=HTML Parameter Mask,HTTP User Parameter Modifier
+xml.parser=org.apache.xerces.parsers.SAXParser
+cookies=cookies
+wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser
+HTTPResponse.parsers=htmlParser wmlParser
+remote_hosts=127.0.0.1
+system.properties=system.properties
+beanshell.server.file=../extras/startup.bsh
+log_level.jmeter.junit=DEBUG
+sampleresult.timestamp.start=true
+jmeter.laf.mac=System
+log_level.jorphan=INFO
+classfinder.functions.contain=.functions.
+user.properties=user.properties
+wmlParser.types=text/vnd.wap.wml
+log_level.jmeter=DEBUG
+classfinder.functions.notContain=.gui.
+htmlParser.types=text/html application/xhtml+xml application/xml text/xml
+upgrade_properties=/bin/upgrade.properties
\ No newline at end of file
diff --git a/testsuite/src/test/jmeter/mongo_test.jmx b/testsuite/src/test/jmeter/mongo_test.jmx
new file mode 100644
index 0000000..b96dcf3
--- /dev/null
+++ b/testsuite/src/test/jmeter/mongo_test.jmx
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="2.4" jmeter="2.9 r1437961">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">10</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">0</stringProp>
+        <longProp name="ThreadGroup.start_time">1362689985000</longProp>
+        <longProp name="ThreadGroup.end_time">1362689985000</longProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Java Request" enabled="true">
+          <elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
+            <collectionProp name="Arguments.arguments">
+            </collectionProp>
+          </elementProp>
+          <stringProp name="classname">org.keycloak.testsuite.performance.BaseJMeterPerformanceTest</stringProp>
+        </JavaSampler>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>
\ No newline at end of file
diff --git a/testsuite/src/test/jmeter/system.properties b/testsuite/src/test/jmeter/system.properties
new file mode 100644
index 0000000..26d24aa
--- /dev/null
+++ b/testsuite/src/test/jmeter/system.properties
@@ -0,0 +1,79 @@
+## Choose implementation of KeycloakSessionFactory
+# keycloak.sessionFactory=picketlink
+keycloak.sessionFactory=mongo
+
+## Configure JPA (just hbm2ddl schema configurable here. Rest of the stuff in META-INF/persistence.xml)
+keycloak.jpa.hbm2ddl.auto=create
+# keycloak.jpa.hbm2ddl.auto=update
+
+
+## Configure MongoDB (Useful just when keycloak.sessionFactory=mongo)
+keycloak.mongodb.host=localhost
+keycloak.mongodb.port=27017
+keycloak.mongodb.databaseName=keycloakPerfTest
+# Should be DB dropped at startup of the test?
+keycloak.mongodb.dropDatabaseOnStartup=true
+
+
+## Specify Keycloak worker class
+keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateRealmsWorker
+# keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateUsersWorker
+# keycloak.perf.workerClass=org.keycloak.testsuite.performance.ReadUsersWorker
+# keycloak.perf.workerClass=org.keycloak.testsuite.performance.RemoveUsersWorker
+
+
+## Properties for CreateRealms test. This test is used to create some realms.
+# Each iteration of single worker thread will add one realm and it will add some roles, defaultRoles, credentials and applications to it
+# Offset where to start creating realms. Count (total number of realms to create) is configurable as number of JMeter threads*loopCount
+# For example: if offset==1 and in JMeter properties we have LoopController.loops=10 and num_threads=2 then we will create 20 realms in total and we will create realms "realm1" - "realm10"
+# NOTE: Count (total number of realms to create) is configurable as number of JMeter threads*loopCount
+keycloak.perf.createRealms.realms.offset=1
+# Count of apps per each realm (For example if count=5, we will create apps like "realm1app1" - "realm1app5" for realm "realm1"
+# and similarly for all other created realms)
+keycloak.perf.createRealms.appsPerRealm=5
+# Count of roles per each realm (For example if count=5, we will create roles like "realm1role1" - "realm1role5" for realm "realm1"
+# and similarly for all other created realms)
+keycloak.perf.createRealms.rolesPerRealm=5
+# Count of default roles per each realm (For example if count=2, we will create roles like "realm1defrole1" and "realm1defrole2"
+# for realm "realm1" and similarly for all other created realms)
+keycloak.perf.createRealms.defaultRolesPerRealm=2
+# Count of roles per each application (For example if count=3 we will have roles "realm1app1role1" - "realm1app1role3" for realm=1 and application=1
+# (if realmsCount=10, appsPerRealm=5 it will be 150 application roles totally)
+keycloak.perf.createRealms.rolesPerApp=3
+# Whether to create required credentials in each realm (If true, we will create "password", "totp" and client-certificate)
+keycloak.perf.createRealms.createRequiredCredentials=true
+
+
+## Properties for CreateUsers test. This test is used to create some users
+# Each iteration of single worker thread will add one user and it will add some default roles, passwords and bind him with some social accounts
+# Each worker will use separate realm dedicated just for him, so each worker will create user1, user2, ... , userN . N (number of users to create per realm)
+# is configurable in JMeter configuration as loopCount. Total number of created users for whole test will be threads*loopCount
+# NOTE: For each thread, the corresponding realm must already exists
+# Realm where to start creating users
+keycloak.perf.createUsers.realms.offset=1
+# Whether to add basic attributes like firstName/lastName/email to each user
+keycloak.perf.createUsers.addBasicUserAttributes=true
+# Whether to add all default roles of realm to this user
+keycloak.perf.createUsers.addDefaultRoles=true
+# Whether to add password to this user
+keycloak.perf.createUsers.addPassword=true
+# Number of social links to create for each user. Possible values are 0, 1, 2, 3 (For 3 it will create Facebook, Twitter and Google)
+keycloak.perf.createUsers.socialLinksPerUserCount=0
+
+
+## Properties for ReadUsers test. This test is used to read some users from DB and alternatively read some of his properties (passwords, roles, scopes, socialLinks)
+keycloak.perf.readUsers.realms.offset=1
+# Number of read users in each iteration
+keycloak.perf.readUsers.readUsersPerIteration=5
+# Number of users to read in each realm. After reading all 2000 users, reading will start again from user1
+keycloak.perf.readUsers.countOfUsersPerRealm=2000
+keycloak.perf.readUsers.readRoles=true
+keycloak.perf.readUsers.readScopes=true
+keycloak.perf.readUsers.readPassword=true
+keycloak.perf.readUsers.readSocialLinks=false
+keycloak.perf.readUsers.searchBySocialLinks=false
+
+
+## Properties for RemoveUsers worker. This test is used to remove some users from DB (and all their stuff actually)
+# Similarly like in CreateUsers test, each worker works just with one realm. Number of removed users depends on JMeter property loopCount
+keycloak.perf.removeUsers.realms.offset=1
diff --git a/testsuite/src/test/resources/META-INF/persistence-performance.xml b/testsuite/src/test/resources/META-INF/persistence-performance.xml
new file mode 100644
index 0000000..1dff641
--- /dev/null
+++ b/testsuite/src/test/resources/META-INF/persistence-performance.xml
@@ -0,0 +1,40 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+             version="1.0">
+    <persistence-unit name="keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+        <class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
+        <class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
+        <class>org.keycloak.services.models.picketlink.mappings.RealmEntity</class>
+        <class>org.keycloak.services.models.picketlink.mappings.ApplicationEntity</class>
+
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+        <properties>
+            <property name="hibernate.connection.url" value="jdbc:mysql://localhost/keycloakPerfTest"/>
+            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
+            <property name="hibernate.connection.username" value="portal"/>
+            <property name="hibernate.connection.password" value="portal"/>
+            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
+            <!--<property name="hibernate.hbm2ddl.auto" value="update" />-->
+            <property name="hibernate.hbm2ddl.auto" value="${keycloak.jpa.hbm2ddl.auto}" />
+            <property name="hibernate.show_sql" value="false" />
+            <property name="hibernate.format_sql" value="true" />
+        </properties>
+    </persistence-unit>
+
+
+</persistence>
\ No newline at end of file