Added performance tests to testsuite to compare Picketlink+JPA+MySQL with Mongo.

This commit is contained in:
mposolda 2013-09-13 20:48:21 +02:00
parent 68ed19f15d
commit 58d862819a
20 changed files with 1092 additions and 5 deletions

View file

@ -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>

View file

@ -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);
}

View file

@ -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));
}
}

72
pom.xml
View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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();
}
}
}
}

View file

@ -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() {
}
}

View file

@ -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() {
}
}

View file

@ -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;
}
}

View file

@ -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() {
}
}

View file

@ -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() {
}
}

View file

@ -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();
}

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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>