diff --git a/examples/as7-eap-demo/server/pom.xml b/examples/as7-eap-demo/server/pom.xml
index 7027dc52cb..f7e619e543 100755
--- a/examples/as7-eap-demo/server/pom.xml
+++ b/examples/as7-eap-demo/server/pom.xml
@@ -118,7 +118,10 @@
com.h2database
h2
- 1.3.161
+
+
+ org.mongodb
+ mongo-java-driver
junit
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 6c62007bb5..d5412cee08 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 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 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 e9be9d467b..da8f04f01c 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("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("socialUsername", socialProviderUserId));
}
+
+ @AttributeProperty
+ public String getRealm() {
+ return (String)getAttribute("realm").getValue();
+ }
+
+ public void setRealm(String realm) {
+ setAttribute(new Attribute("realm", realm));
+ }
}
diff --git a/pom.xml b/pom.xml
index c2a58bcd10..9ab5c51f5b 100755
--- a/pom.xml
+++ b/pom.xml
@@ -12,6 +12,13 @@
3.0.4.Final
1.0.0.Beta12
2.5.0.Beta6
+ 2.11.2
+ 3.1.1.GA
+ 1.0.1.Final
+ 3.6.6.Final
+ 1.3.161
+ 1.6.1
+ 1.6.1
http://keycloak.org
@@ -177,7 +184,7 @@
org.jboss.logging
jboss-logging
- 3.1.1.GA
+ ${jboss.logging.version}
junit
@@ -187,7 +194,17 @@
org.hibernate.javax.persistence
hibernate-jpa-2.0-api
- 1.0.1.Final
+ ${hibernate.javax.persistence.version}
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.entitymanager.version}
com.google.api-client
@@ -259,6 +276,42 @@
de.flapdoodle.embed.mongo
1.27
+
+ org.apache.jmeter
+ ApacheJMeter_java
+ 2.9
+
+
+ dom4j
+ dom4j
+ ${dom4j.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.25
+
+
+
+ org.jboss.arquillian
+ arquillian-bom
+ 1.1.1.Final
+ pom
+ import
+
+
+ org.jboss.arquillian.extension
+ arquillian-drone-bom
+ 1.2.0.Beta1
+ pom
+ import
+
@@ -340,6 +393,21 @@
false
+
+ com.lazerycode.jmeter
+ jmeter-maven-plugin
+ 1.8.1
+
+
+ com.lazerycode.jmeter
+ jmeter-analysis-maven-plugin
+ 1.0.4
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.2
+
diff --git a/services/pom.xml b/services/pom.xml
index 8696a5d1e0..b35bef4135 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -147,10 +147,12 @@
org.picketlink
picketlink-common
+ provided
org.mongodb
mongo-java-driver
+ provided
de.flapdoodle.embed
@@ -170,13 +172,11 @@
com.h2database
h2
- 1.3.161
test
org.hibernate
hibernate-entitymanager
- 3.6.6.Final
test
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 838a6a4b60..71cd3a1e58 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 2165165d6a..85c9fcdb0d 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 7cfe6f5096..46b1c2622d 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;
+ }
}
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 7c5278d899..adb5aa3e7e 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -182,6 +182,10 @@
org.seleniumhq.selenium
selenium-java
+
+ org.apache.jmeter
+ ApacheJMeter_java
+
@@ -193,6 +197,17 @@
1.6
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+
@@ -219,5 +234,156 @@
+
+
+ performance-tests
+
+
+
+ com.lazerycode.jmeter
+ jmeter-maven-plugin
+
+
+ jmeter-tests
+ verify
+
+ jmeter
+
+
+
+
+
+ org.keycloak
+ keycloak-testsuite
+ ${project.version}
+ test-jar
+
+
+ org.keycloak
+ keycloak-services
+ ${project.version}
+
+
+ org.jboss.resteasy
+ jaxrs-api
+ ${resteasy.version}
+
+
+ org.jboss.resteasy
+ resteasy-jaxrs
+ ${resteasy.version}
+
+
+ log4j
+ log4j
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-simple
+
+
+ commons-io
+ commons-io
+
+
+
+
+ org.jboss.logging
+ jboss-logging
+ ${jboss.logging.version}
+
+
+ org.picketlink
+ picketlink-idm-impl
+ ${picketlink.version}
+
+
+ org.picketlink
+ picketlink-idm-simple-schema
+ ${picketlink.version}
+
+
+ org.picketlink
+ picketlink-config
+ ${picketlink.version}
+
+
+ org.mongodb
+ mongo-java-driver
+ ${mongo.driver.version}
+
+
+
+
+ org.hibernate.javax.persistence
+ hibernate-jpa-2.0-api
+ ${hibernate.javax.persistence.version}
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.entitymanager.version}
+
+
+ dom4j
+ dom4j
+ ${dom4j.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+
+
+ com.lazerycode.jmeter
+ jmeter-analysis-maven-plugin
+
+
+ jmeter-tests-analyze
+ verify
+
+ analyze
+
+
+
+ ${project.build.directory}/jmeter/results
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 0000000000..bc38e6d7bd
--- /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 Marek Posolda
+ */
+public class BaseJMeterPerformanceTest extends AbstractJavaSamplerClient {
+
+
+ private static FutureTask factoryProvider = new FutureTask(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 0000000000..a3b37e091f
--- /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 Marek Posolda
+ */
+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 0000000000..f4279442d3
--- /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 Marek Posolda
+ */
+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 ; iMarek Posolda
+ */
+public class PerfTestUtils {
+
+ public static T readSystemProperty(String propertyName, Class 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 0000000000..1439919512
--- /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 Marek Posolda
+ */
+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 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 0000000000..966024ac73
--- /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 Marek Posolda
+ */
+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 0000000000..6060f49a3f
--- /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 Marek Posolda
+ */
+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 0000000000..39b6ce46ab
--- /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 0000000000..b96dcf36d1
--- /dev/null
+++ b/testsuite/src/test/jmeter/mongo_test.jmx
@@ -0,0 +1,39 @@
+
+
+
+
+
+ false
+ false
+
+
+
+
+
+
+
+ continue
+
+ false
+ 10
+
+ 1
+ 0
+ 1362689985000
+ 1362689985000
+ false
+
+
+
+
+
+
+
+
+
+ org.keycloak.testsuite.performance.BaseJMeterPerformanceTest
+
+
+
+
+
\ 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 0000000000..26d24aab9b
--- /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 0000000000..1dff64183f
--- /dev/null
+++ b/testsuite/src/test/resources/META-INF/persistence-performance.xml
@@ -0,0 +1,40 @@
+
+
+ org.hibernate.ejb.HibernatePersistence
+
+ org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity
+ org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity
+ org.keycloak.services.models.picketlink.mappings.RealmEntity
+ org.keycloak.services.models.picketlink.mappings.ApplicationEntity
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file