keycloak-memoizeit
Changes
examples/as7-eap-demo/server/pom.xml 5(+4 -1)
model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java 16(+16 -0)
pom.xml 72(+70 -2)
services/pom.xml 4(+2 -2)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java 4(+4 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java 11(+11 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java 12(+12 -0)
testsuite/pom.xml 166(+166 -0)
testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java 141(+141 -0)
testsuite/src/test/jmeter/jmeter.properties 20(+20 -0)
testsuite/src/test/jmeter/mongo_test.jmx 39(+39 -0)
testsuite/src/test/jmeter/system.properties 79(+79 -0)
Details
examples/as7-eap-demo/server/pom.xml 5(+4 -1)
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();
+
+}
testsuite/src/test/jmeter/jmeter.properties 20(+20 -0)
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
testsuite/src/test/jmeter/mongo_test.jmx 39(+39 -0)
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
testsuite/src/test/jmeter/system.properties 79(+79 -0)
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