Merge pull request #490 from mposolda/performance
Added module performance-web for testing Keycloak endpoints with JMeter
This commit is contained in:
commit
6f023fcae4
23 changed files with 1775 additions and 31 deletions
6
pom.xml
6
pom.xml
|
@ -174,7 +174,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-undertow</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
|
@ -360,7 +360,7 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_java</artifactId>
|
||||
<version>2.9</version>
|
||||
<version>2.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dom4j</groupId>
|
||||
|
@ -513,7 +513,7 @@
|
|||
<plugin>
|
||||
<groupId>com.lazerycode.jmeter</groupId>
|
||||
<artifactId>jmeter-maven-plugin</artifactId>
|
||||
<version>1.8.1</version>
|
||||
<version>1.9.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.lazerycode.jmeter</groupId>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<artifactId>keycloak-testsuite-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-4-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ public class KeycloakServer {
|
|||
public static class KeycloakServerConfig {
|
||||
private String host = "localhost";
|
||||
private int port = 8081;
|
||||
private int workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8;
|
||||
private String resourcesHome;
|
||||
|
||||
public String getHost() {
|
||||
|
@ -84,6 +85,14 @@ public class KeycloakServer {
|
|||
public void setResourcesHome(String resourcesHome) {
|
||||
this.resourcesHome = resourcesHome;
|
||||
}
|
||||
|
||||
public int getWorkerThreads() {
|
||||
return workerThreads;
|
||||
}
|
||||
|
||||
public void setWorkerThreads(int workerThreads) {
|
||||
this.workerThreads = workerThreads;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T loadJson(InputStream is, Class<T> type) {
|
||||
|
@ -95,6 +104,10 @@ public class KeycloakServer {
|
|||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
bootstrapKeycloakServer(args);
|
||||
}
|
||||
|
||||
public static KeycloakServer bootstrapKeycloakServer(String[] args) throws Throwable {
|
||||
KeycloakServerConfig config = new KeycloakServerConfig();
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
|
@ -137,6 +150,10 @@ public class KeycloakServer {
|
|||
config.setResourcesHome(dir.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (System.getProperties().containsKey("undertowWorkerThreads")) {
|
||||
int undertowWorkerThreads = Integer.parseInt(System.getProperty("undertowWorkerThreads"));
|
||||
config.setWorkerThreads(undertowWorkerThreads);
|
||||
}
|
||||
|
||||
final KeycloakServer keycloak = new KeycloakServer(config);
|
||||
keycloak.sysout = true;
|
||||
|
@ -158,6 +175,8 @@ public class KeycloakServer {
|
|||
keycloak.stop();
|
||||
}
|
||||
});
|
||||
|
||||
return keycloak;
|
||||
}
|
||||
|
||||
private KeycloakServerConfig config;
|
||||
|
@ -238,7 +257,10 @@ public class KeycloakServer {
|
|||
ResteasyDeployment deployment = new ResteasyDeployment();
|
||||
deployment.setApplicationClass(KeycloakApplication.class.getName());
|
||||
|
||||
Builder builder = Undertow.builder().addListener(config.getPort(), config.getHost());
|
||||
Builder builder = Undertow.builder()
|
||||
.addHttpListener(config.getPort(), config.getHost())
|
||||
.setWorkerThreads(config.getWorkerThreads())
|
||||
.setIoThreads(config.getWorkerThreads() / 8);
|
||||
|
||||
server = new UndertowJaxrsServer().start(builder);
|
||||
|
||||
|
|
106
testsuite/performance-web/README.md
Normal file
106
testsuite/performance-web/README.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
Keycloak Web Performance Testsuite
|
||||
==================================
|
||||
To run web performance testsuite, you need to:
|
||||
1) Run Keycloak server
|
||||
2) Add some users into your Keycloak
|
||||
3) Run JMeter performance tests
|
||||
|
||||
Keycloak server
|
||||
---------------
|
||||
In this project you can run:
|
||||
|
||||
```shell
|
||||
mvn exec:java -Pkeycloak-perf-server
|
||||
````
|
||||
|
||||
which will execute embedded Undertow server with:
|
||||
* Keycloak server
|
||||
* Performance-tools for mass adding of new users
|
||||
* Simple web application for testing performance
|
||||
It will also automatically import realm "perf-realm" into Keycloak from file src/main/resources/perfrealm.json
|
||||
|
||||
Note that by default it will use in-memory H2 database, which means that all changes (for example all added users) are discarded after server restart. For performance testing, it's recommended to use some database like PostgreSQL or MySQL
|
||||
|
||||
To run server with PostgreSQL you may use command like this (change host,port,dbName and credentials according your DB configuration):
|
||||
```shell
|
||||
mvn exec:java -Pkeycloak-perf-server -Dhibernate.connection.url=jdbc:postgresql://localhost:5432/keycloak_perf -Dhibernate.connection.driver_class=org.postgresql.Driver -Dhibernate.connection.username=postgres -Dhibernate.connection.password=postgres -Dhibernate.connection.autocommit=false
|
||||
````
|
||||
|
||||
To run server with MySQL you may use command like this (change host,port,dbName and credentials according your DB configuration):
|
||||
```shell
|
||||
mvn exec:java -Pkeycloak-perf-server -Dhibernate.connection.url=jdbc:mysql://localhost/keycloak_perf -Dhibernate.connection.driver_class=com.mysql.jdbc.Driver -Dhibernate.connection.username=portal -Dhibernate.connection.password=portal -Dhibernate.connection.autocommit=false
|
||||
````
|
||||
|
||||
To run server with Mongo you may use command like this (change host,port,dbName and credentials according your DB configuration):
|
||||
```shell
|
||||
mvn exec:java -Pkeycloak-perf-server -Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.db=keycloak-perf
|
||||
````
|
||||
|
||||
To enable cache, you can add additional property:
|
||||
```shell
|
||||
-Dkeycloak.model.cache.provider=simple
|
||||
````
|
||||
|
||||
Adding users
|
||||
-----------------
|
||||
|
||||
Performance test is using users with prefix "user" (so users like "user-0", "user-1", ... , "user-123456" etc). So you first need to add some of these users into your database.
|
||||
|
||||
For checking users count, you can open this URL:
|
||||
```shell
|
||||
http://localhost:8081/keycloak-tools/perf/perf-realm/get-users-count?prefix=user
|
||||
````
|
||||
|
||||
For adding 10000 new users into your database (will start from last added user, so you don't need to explicitly check how many users to create are needed:
|
||||
```shell
|
||||
http://localhost:8081/keycloak-tools/perf/perf-realm/create-available-users?prefix=user&count=10000&batch=100&roles=user
|
||||
````
|
||||
|
||||
Seeing progress of job for creating users
|
||||
```shell
|
||||
http://localhost:8081/keycloak-tools/perf/jobs
|
||||
````
|
||||
|
||||
Note that with default H2 are all data automatically cleared after server restart. So it's recommended to use different DB like PostgreSQL or Mongo.
|
||||
|
||||
|
||||
Execute performance test
|
||||
------------------------
|
||||
|
||||
When server is started and some users are created, you can run performance test. It's possible to run it from Command line with:
|
||||
|
||||
```shell
|
||||
mvn verify -Pperformance-test
|
||||
````
|
||||
|
||||
By default, test is using Keycloak on localhost:8081 and 50 concurrent clients (threads) and each client doing 50 test iterations. Each iterations is:
|
||||
- Login user into KC and retrieve code
|
||||
- Exchange code for accessToken
|
||||
- Refresh token 2 times
|
||||
- Logout user
|
||||
|
||||
Each client is using separate username, so actually you need at least 50 users created into your DB (users "user-0", "user-1", ... "user-49" . See above)
|
||||
|
||||
ATM it's possible to adjust behaviour with properties:
|
||||
* host --- Keycloak host ("localhost" by default)
|
||||
* port --- Keycloak port ("8081" by default)
|
||||
* userPrefix --- prefix of users ("user" by default)
|
||||
* concurrentUsers --- Number of concurrent clients (50 by default). RampUp time is configured to start 5 new users each second, so with 50 users are all clients started in like 10 seconds.
|
||||
* iterationsPerUser --- Number of iterations per each client (50 by default). So by default we have 50*50 = 2500 iterations in total per whole test
|
||||
* refreshTokenRequestsPerIteration --- Number of refresh token iterations (2 by default)
|
||||
|
||||
You can change configuration by adding some properties into command line for example to start just with 10 concurrent clients and 10 iterations per client (so just 100 test iterations) you can use:
|
||||
```shell
|
||||
mvn verify -Pperformance-test -DconcurrentUsers=10 -DiterationsPerUser=10
|
||||
````
|
||||
|
||||
After triggering test are results in file target/jmeter/results/aggregatedRequests-durations-<TIMESTAMP>-keycloak_web_perf_test.html
|
||||
|
||||
Execute performance test from JMeter GUI
|
||||
----------------------------------------
|
||||
You can run
|
||||
```shell
|
||||
mvn jmeter:gui
|
||||
````
|
||||
|
||||
and then open file src/test/jmeter/keycloak_web_perf_test.jmx and trigger test from JMeter GUI. It may be good as you can see the progress of whole test during execution.
|
248
testsuite/performance-web/pom.xml
Normal file
248
testsuite/performance-web/pom.xml
Normal file
|
@ -0,0 +1,248 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-testsuite-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-4-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-testsuite-performance-web</artifactId>
|
||||
<name>Keycloak TestSuite for Web Performance</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-testsuite-integration</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-testsuite-tools</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<classifier>classes</classifier>
|
||||
</dependency>
|
||||
|
||||
<!-- Needed by undertow -->
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||
<version>1.0.0.Final</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Resteasy deps specified here as we want latest version of them -->
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>jaxrs-api</artifactId>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<version>${resteasy.version.latest}</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>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-crypto</artifactId>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-multipart-provider</artifactId>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson-provider</artifactId>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-undertow</artifactId>
|
||||
<version>${resteasy.version.latest}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_java</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcmail-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcmail-jdk15</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-parsers</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<workingDirectory>${project.basedir}</workingDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
<profile>
|
||||
<id>keycloak-perf-server</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>org.keycloak.testsuite.performance.web.KeycloakPerfServer</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>performance-test</id>
|
||||
|
||||
<properties>
|
||||
<host>localhost</host>
|
||||
<port>8081</port>
|
||||
<userPrefix>user</userPrefix>
|
||||
<concurrentUsers>50</concurrentUsers>
|
||||
<iterationsPerUser>50</iterationsPerUser>
|
||||
<refreshTokenRequestsPerIteration>2</refreshTokenRequestsPerIteration>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.lazerycode.jmeter</groupId>
|
||||
<artifactId>jmeter-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<suppressJMeterOutput>false</suppressJMeterOutput>
|
||||
<propertiesSystem>
|
||||
<host>${host}</host>
|
||||
<port>${port}</port>
|
||||
<userPrefix>${userPrefix}</userPrefix>
|
||||
<concurrentUsers>${concurrentUsers}</concurrentUsers>
|
||||
<iterationsPerUser>${iterationsPerUser}</iterationsPerUser>
|
||||
<refreshTokenRequestsPerIteration>${refreshTokenRequestsPerIteration}</refreshTokenRequestsPerIteration>
|
||||
</propertiesSystem>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jmeter-tests</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>jmeter</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
</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>
|
||||
|
||||
<requestGroups>
|
||||
<requestGroup>
|
||||
<name>aggregatedRequests</name>
|
||||
<pattern>* request</pattern>
|
||||
</requestGroup>
|
||||
<requestGroup>
|
||||
<name>codes</name>
|
||||
<pattern>**/perf-app/perf-servlet?code=*</pattern>
|
||||
</requestGroup>
|
||||
</requestGroups>
|
||||
|
||||
<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>
|
|
@ -0,0 +1,97 @@
|
|||
package org.keycloak.testsuite.performance.web;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
|
||||
import io.undertow.server.handlers.resource.ClassPathResourceManager;
|
||||
import io.undertow.servlet.Servlets;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.FilterInfo;
|
||||
import io.undertow.servlet.api.ServletInfo;
|
||||
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
|
||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||
import org.keycloak.services.filters.ClientConnectionFilter;
|
||||
import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
||||
import org.keycloak.test.tools.KeycloakTestApplication;
|
||||
import org.keycloak.testutils.KeycloakServer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class KeycloakPerfServer {
|
||||
|
||||
private KeycloakServer keycloakServer;
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
// TODO: should be better than programmatic setup here, but don't copy persistence.xml again...
|
||||
System.setProperty("hibernate.hbm2ddl.auto", "update");
|
||||
|
||||
if (System.getProperty("undertowWorkerThreads") == null) {
|
||||
System.setProperty("undertowWorkerThreads", "256");
|
||||
}
|
||||
|
||||
KeycloakServer keycloakServer = KeycloakServer.bootstrapKeycloakServer(args);
|
||||
System.out.println("Keycloak server bootstrapped");
|
||||
|
||||
KeycloakSessionFactoryHolder.setKeycloakSessionFactory(keycloakServer.getSessionFactory());
|
||||
new KeycloakPerfServer(keycloakServer).start();
|
||||
}
|
||||
|
||||
public KeycloakPerfServer(KeycloakServer keycloakServer) {
|
||||
this.keycloakServer = keycloakServer;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
importPerfRealm();
|
||||
deployPerfTools();
|
||||
deployPerfApp();
|
||||
}
|
||||
|
||||
protected void importPerfRealm() {
|
||||
InputStream perfRealmStream = KeycloakPerfServer.class.getClassLoader().getResourceAsStream("perfrealm.json");
|
||||
keycloakServer.importRealm(perfRealmStream);
|
||||
}
|
||||
|
||||
protected void deployPerfTools() {
|
||||
ResteasyDeployment deployment = new ResteasyDeployment();
|
||||
deployment.setApplicationClass(KeycloakToolsApplication.class.getName());
|
||||
|
||||
UndertowJaxrsServer server = keycloakServer.getServer();
|
||||
|
||||
DeploymentInfo di = server.undertowDeployment(deployment, "");
|
||||
di.setClassLoader(KeycloakTestApplication.class.getClassLoader());
|
||||
di.setContextPath("/keycloak-tools");
|
||||
di.setDeploymentName("KeycloakTools");
|
||||
|
||||
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
|
||||
di.addFilter(filter);
|
||||
di.addFilterUrlMapping("SessionFilter", "/perf/*", DispatcherType.REQUEST);
|
||||
|
||||
FilterInfo connectionFilter = Servlets.filter("ClientConnectionFilter", ClientConnectionFilter.class);
|
||||
di.addFilter(connectionFilter);
|
||||
di.addFilterUrlMapping("ClientConnectionFilter", "/perf/*", DispatcherType.REQUEST);
|
||||
|
||||
server.deploy(di);
|
||||
|
||||
System.out.println("Keycloak tools deployed");
|
||||
}
|
||||
|
||||
protected void deployPerfApp() {
|
||||
DeploymentInfo deploymentInfo = new DeploymentInfo();
|
||||
deploymentInfo.setClassLoader(getClass().getClassLoader());
|
||||
deploymentInfo.setDeploymentName("PerfApp");
|
||||
deploymentInfo.setContextPath("/perf-app");
|
||||
|
||||
ServletInfo servlet = new ServletInfo("PerfAppServlet", PerfAppServlet.class);
|
||||
servlet.addMapping("/perf-servlet/*");
|
||||
|
||||
deploymentInfo.addServlet(servlet);
|
||||
|
||||
deploymentInfo.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader()));
|
||||
|
||||
keycloakServer.getServer().deploy(deploymentInfo);
|
||||
|
||||
System.out.println("PerfApp deployed");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.keycloak.testsuite.performance.web;
|
||||
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* Static holder to allow sharing ProviderSessionFactory among different JAX-RS applications
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class KeycloakSessionFactoryHolder {
|
||||
|
||||
private static KeycloakSessionFactory keycloakSessionFactory;
|
||||
|
||||
public static KeycloakSessionFactory getKeycloakSessionFactory() {
|
||||
return keycloakSessionFactory;
|
||||
}
|
||||
|
||||
public static void setKeycloakSessionFactory(KeycloakSessionFactory keycloakSessionFactory) {
|
||||
KeycloakSessionFactoryHolder.keycloakSessionFactory = keycloakSessionFactory;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.keycloak.testsuite.performance.web;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.ws.rs.core.Application;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
||||
import org.jboss.resteasy.core.Dispatcher;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.test.tools.PerfTools;
|
||||
|
||||
/**
|
||||
* Modified version of {@link org.keycloak.test.tools.KeycloakTestApplication}, which shares ProviderSessionFactory with KeycloakApplication
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class KeycloakToolsApplication extends Application {
|
||||
|
||||
protected KeycloakSessionFactory keycloakSessionFactory;
|
||||
protected Set<Class<?>> classes = new HashSet<Class<?>>();
|
||||
protected Set<Object> singletons = new HashSet<Object>();
|
||||
|
||||
public KeycloakToolsApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
|
||||
this.keycloakSessionFactory = KeycloakSessionFactoryHolder.getKeycloakSessionFactory();
|
||||
context.setAttribute(KeycloakSessionFactory.class.getName(), this.keycloakSessionFactory);
|
||||
singletons.add(new PerfTools(keycloakSessionFactory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> getSingletons() {
|
||||
return singletons;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
package org.keycloak.testsuite.performance.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.resteasy.security.PemUtils;
|
||||
import org.json.JSONObject;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.services.resources.TokenService;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
/**
|
||||
* TODO: Remove from here and instead merge with org.keycloak.testsuite.OAuthClient
|
||||
*
|
||||
*@author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class OAuthClient {
|
||||
|
||||
private String baseUrl = "http://localhost:8081/auth";
|
||||
|
||||
private String realm = "perf-realm";
|
||||
|
||||
private String responseType = OAuth2Constants.CODE;
|
||||
|
||||
private String grantType = "authorization_code";
|
||||
|
||||
private String clientId = "perf-app";
|
||||
|
||||
private String redirectUri = "http://localhost:8081/perf-app/perf-servlet";
|
||||
|
||||
private String state = "123";
|
||||
|
||||
private PublicKey realmPublicKey;
|
||||
|
||||
public OAuthClient() {
|
||||
try {
|
||||
JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/perfrealm.json")));
|
||||
realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve realm public key", e);
|
||||
}
|
||||
}
|
||||
|
||||
public AccessTokenResponse doAccessTokenRequest(String code, String password) {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(getAccessTokenUrl());
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||
if (grantType != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
|
||||
}
|
||||
if (code != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
|
||||
}
|
||||
if (redirectUri != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
|
||||
}
|
||||
if (clientId != null && password != null) {
|
||||
String authorization = BasicAuthHelper.createHeader(clientId, password);
|
||||
post.setHeader("Authorization", authorization);
|
||||
}
|
||||
else if (clientId != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity = null;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
post.setEntity(formEntity);
|
||||
|
||||
try {
|
||||
return new AccessTokenResponse(client.execute(post));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve access token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password) throws Exception {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl());
|
||||
|
||||
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
|
||||
post.setHeader("Authorization", authorization);
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||
parameters.add(new BasicNameValuePair("username", username));
|
||||
parameters.add(new BasicNameValuePair("password", password));
|
||||
|
||||
UrlEncodedFormEntity formEntity;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
post.setEntity(formEntity);
|
||||
|
||||
return new AccessTokenResponse(client.execute(post));
|
||||
}
|
||||
|
||||
public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState));
|
||||
|
||||
return client.execute(get);
|
||||
}
|
||||
|
||||
public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(getRefreshTokenUrl());
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||
if (grantType != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
}
|
||||
if (clientId != null && password != null) {
|
||||
String authorization = BasicAuthHelper.createHeader(clientId, password);
|
||||
post.setHeader("Authorization", authorization);
|
||||
}
|
||||
else if (clientId != null) {
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
post.setEntity(formEntity);
|
||||
|
||||
try {
|
||||
return new AccessTokenResponse(client.execute(post));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve access token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public AccessToken verifyToken(String token) {
|
||||
try {
|
||||
return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
|
||||
} catch (VerificationException e) {
|
||||
throw new RuntimeException("Failed to verify token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyCode(String code) {
|
||||
if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
|
||||
throw new RuntimeException("Failed to verify code");
|
||||
}
|
||||
}
|
||||
|
||||
public RefreshToken verifyRefreshToken(String refreshToken) {
|
||||
try {
|
||||
JWSInput jws = new JWSInput(refreshToken);
|
||||
if (!RSAProvider.verify(jws, realmPublicKey)) {
|
||||
throw new RuntimeException("Invalid refresh token");
|
||||
}
|
||||
return jws.readJsonContent(RefreshToken.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid refresh token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public String getLoginFormUrl() {
|
||||
UriBuilder b = TokenService.loginPageUrl(UriBuilder.fromUri(baseUrl));
|
||||
if (responseType != null) {
|
||||
b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType);
|
||||
}
|
||||
if (clientId != null) {
|
||||
b.queryParam(OAuth2Constants.CLIENT_ID, clientId);
|
||||
}
|
||||
if (redirectUri != null) {
|
||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
if (state != null) {
|
||||
b.queryParam(OAuth2Constants.STATE, state);
|
||||
}
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getAccessTokenUrl() {
|
||||
UriBuilder b = TokenService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl));
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getLogoutUrl(String redirectUri, String sessionState) {
|
||||
UriBuilder b = TokenService.logoutUrl(UriBuilder.fromUri(baseUrl));
|
||||
if (redirectUri != null) {
|
||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
if (sessionState != null) {
|
||||
b.queryParam("session_state", sessionState);
|
||||
}
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
||||
UriBuilder b = TokenService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl));
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getRefreshTokenUrl() {
|
||||
UriBuilder b = TokenService.refreshUrl(UriBuilder.fromUri(baseUrl));
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public OAuthClient realm(String realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
}
|
||||
public OAuthClient realmPublicKey(PublicKey key) {
|
||||
this.realmPublicKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient clientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient redirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient responseType(String responseType) {
|
||||
this.responseType = responseType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuthClient state(String state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public static class AuthorizationCodeResponse {
|
||||
|
||||
private String code;
|
||||
private String state;
|
||||
private String error;
|
||||
|
||||
public AuthorizationCodeResponse(OAuthClient client, HttpServletRequest req) {
|
||||
code = req.getParameter(OAuth2Constants.CODE);
|
||||
state = req.getParameter(OAuth2Constants.STATE);
|
||||
error = req.getParameter(OAuth2Constants.ERROR);
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class AccessTokenResponse {
|
||||
private int statusCode;
|
||||
|
||||
private String accessToken;
|
||||
private String tokenType;
|
||||
private int expiresIn;
|
||||
private String refreshToken;
|
||||
private String idToken;
|
||||
private String sessionState;
|
||||
|
||||
private String error;
|
||||
|
||||
public AccessTokenResponse(HttpResponse response) throws Exception {
|
||||
statusCode = response.getStatusLine().getStatusCode();
|
||||
if (!"application/json".equals(response.getHeaders("Content-Type")[0].getValue())) {
|
||||
throw new RuntimeException("Invalid content type");
|
||||
}
|
||||
|
||||
String s = IOUtils.toString(response.getEntity().getContent());
|
||||
JSONObject responseJson = new JSONObject(s);
|
||||
|
||||
if (statusCode == 200) {
|
||||
accessToken = responseJson.getString("access_token");
|
||||
tokenType = responseJson.getString("token_type");
|
||||
expiresIn = responseJson.getInt("expires_in");
|
||||
idToken = responseJson.optString("id_token");
|
||||
sessionState = responseJson.optString("session-state");
|
||||
|
||||
if (responseJson.has(OAuth2Constants.REFRESH_TOKEN)) {
|
||||
refreshToken = responseJson.getString(OAuth2Constants.REFRESH_TOKEN);
|
||||
}
|
||||
} else {
|
||||
error = responseJson.getString(OAuth2Constants.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public int getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public String getIdToken() {
|
||||
return idToken;
|
||||
}
|
||||
|
||||
public String getSessionState() {
|
||||
return sessionState;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package org.keycloak.testsuite.performance.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import freemarker.cache.ClassTemplateLoader;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.Template;
|
||||
import freemarker.template.TemplateException;
|
||||
import org.keycloak.adapters.AdapterConstants;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PerfAppServlet extends HttpServlet {
|
||||
|
||||
private Template indexTemplate;
|
||||
private OAuthClient oauthClient;
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException {
|
||||
try {
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.setTemplateLoader(new ClassTemplateLoader(getClass(), "/"));
|
||||
indexTemplate = cfg.getTemplate("perf-app-resources/index.ftl");
|
||||
|
||||
oauthClient = new OAuthClient();
|
||||
} catch (IOException ioe) {
|
||||
throw new ServletException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentType("text/html");
|
||||
String action = req.getParameter("action");
|
||||
String actionDone = null;
|
||||
|
||||
if (action != null) {
|
||||
if (action.equals("code")) {
|
||||
keycloakLoginRedirect(req, resp);
|
||||
return;
|
||||
} else if (action.equals("exchangeCode")) {
|
||||
exchangeCodeForToken(req, resp);
|
||||
actionDone = "Token retrieved";
|
||||
} else if (action.equals("refresh")) {
|
||||
refreshToken(req, resp);
|
||||
actionDone = "Token refreshed";
|
||||
} else if (action.equals("logout")) {
|
||||
logoutRedirect(req, resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String code = req.getParameter("code");
|
||||
if (code != null) {
|
||||
req.getSession().setAttribute("code", code);
|
||||
actionDone = "Code retrieved";
|
||||
}
|
||||
|
||||
String freemarkerRedirect = freemarkerRedirect(req, resp, actionDone);
|
||||
resp.getWriter().println(freemarkerRedirect);
|
||||
resp.getWriter().flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
if (req.getRequestURI().endsWith(AdapterConstants.K_LOGOUT)) {
|
||||
// System.out.println("Logout callback triggered");
|
||||
resp.setStatus(204);
|
||||
}
|
||||
}
|
||||
|
||||
protected void keycloakLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String loginUrl = oauthClient.getLoginFormUrl();
|
||||
resp.sendRedirect(loginUrl);
|
||||
}
|
||||
|
||||
protected void exchangeCodeForToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String code = (String)req.getSession().getAttribute("code");
|
||||
OAuthClient.AccessTokenResponse atResponse = oauthClient.doAccessTokenRequest(code, "password");
|
||||
|
||||
updateTokensInSession(req, atResponse);
|
||||
}
|
||||
|
||||
protected void refreshToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String refreshToken = (String)req.getSession().getAttribute("refreshToken");
|
||||
OAuthClient.AccessTokenResponse atResponse = oauthClient.doRefreshTokenRequest(refreshToken, "password");
|
||||
|
||||
updateTokensInSession(req, atResponse);
|
||||
}
|
||||
|
||||
private void updateTokensInSession(HttpServletRequest req, OAuthClient.AccessTokenResponse atResponse) {
|
||||
String accessToken = atResponse.getAccessToken();
|
||||
String refreshToken = atResponse.getRefreshToken();
|
||||
AccessToken accessTokenParsed = oauthClient.verifyToken(accessToken);
|
||||
RefreshToken refreshTokenParsed = oauthClient.verifyRefreshToken(refreshToken);
|
||||
req.getSession().setAttribute("accessToken", accessToken);
|
||||
req.getSession().setAttribute("refreshToken", refreshToken);
|
||||
req.getSession().setAttribute("accessTokenParsed", accessTokenParsed);
|
||||
req.getSession().setAttribute("refreshTokenParsed", refreshTokenParsed);
|
||||
}
|
||||
|
||||
protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
// Invalidate http session
|
||||
req.getSession(false).invalidate();
|
||||
|
||||
String logoutURL = oauthClient.getLogoutUrl(oauthClient.getRedirectUri(), null);
|
||||
resp.sendRedirect(logoutURL);
|
||||
}
|
||||
|
||||
private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp, String actionDone) throws ServletException, IOException {
|
||||
AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
|
||||
RefreshToken refreshTokenParsed = (RefreshToken)req.getSession().getAttribute("refreshTokenParsed");
|
||||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
attributes.put("requestURI", req.getRequestURI());
|
||||
attributes.put("code", req.getSession().getAttribute("code"));
|
||||
attributes.put("accessToken", req.getSession().getAttribute("accessToken"));
|
||||
attributes.put("refreshToken", req.getSession().getAttribute("refreshToken"));
|
||||
attributes.put("accessTokenParsed", accessTokenParsed);
|
||||
attributes.put("refreshTokenParsed", refreshTokenParsed);
|
||||
attributes.put("actionDone", actionDone);
|
||||
|
||||
if (accessTokenParsed != null) {
|
||||
attributes.put("accessTokenExpiration", Time.toDate(accessTokenParsed.getExpiration()).toString());
|
||||
}
|
||||
if (refreshTokenParsed != null) {
|
||||
attributes.put("refreshTokenExpiration", Time.toDate(refreshTokenParsed.getExpiration()).toString());
|
||||
}
|
||||
|
||||
try {
|
||||
Writer out = new StringWriter();
|
||||
indexTemplate.process(attributes, out);
|
||||
return out.toString();
|
||||
} catch (TemplateException te) {
|
||||
throw new ServletException(te);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>PerfTest</title>
|
||||
<script>
|
||||
function updateElementWithToken(tokenStr, elementId) {
|
||||
if (tokenStr && tokenStr != "") {
|
||||
var tokenParsed = JSON.stringify(JSON.parse(decodeURIComponent(escape(window.atob( tokenStr.split('.')[1] )))), null, " ");
|
||||
document.getElementById(elementId).innerHTML = tokenParsed;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p><a href="${requestURI}?action=code">Login and get code</a> | <a href="${requestURI}?action=exchangeCode">Exchange code</a> | <a
|
||||
href="${requestURI}?action=refresh">Refresh token</a> | <a href="${requestURI}?action=logout">Logout</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<#if code??>
|
||||
<b>Code Available</b><br>
|
||||
Code=${code} <br>
|
||||
<hr />
|
||||
</#if>
|
||||
|
||||
<#if accessToken??>
|
||||
<b>Access Token Available</b><br>
|
||||
AccessToken=${accessToken} <br>
|
||||
Username=${accessTokenParsed.preferredUsername} <br>
|
||||
SessionState=${accessTokenParsed.sessionState} <br>
|
||||
Expiration=${accessTokenExpiration} <br>
|
||||
<hr />
|
||||
</#if>
|
||||
|
||||
<#if refreshToken??>
|
||||
<b>Refresh token available</b><br>
|
||||
RefreshToken=${refreshToken} <br>
|
||||
Expiration=${refreshTokenExpiration} <br>
|
||||
<hr />
|
||||
</#if>
|
||||
|
||||
<#if actionDone??>
|
||||
RequestAction=${actionDone}
|
||||
<hr />
|
||||
</#if>
|
||||
|
||||
</p>
|
||||
<br><br>
|
||||
</body>
|
||||
</html>
|
108
testsuite/performance-web/src/main/resources/perfrealm.json
Normal file
108
testsuite/performance-web/src/main/resources/perfrealm.json
Normal file
|
@ -0,0 +1,108 @@
|
|||
{
|
||||
"id": "perf-realm",
|
||||
"realm": "perf-realm",
|
||||
"enabled": true,
|
||||
"sslNotRequired": true,
|
||||
"registrationAllowed": true,
|
||||
"resetPasswordAllowed": true,
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [ "password" ],
|
||||
"defaultRoles": [ "user" ],
|
||||
"smtpServer": {
|
||||
"from": "auto@keycloak.org",
|
||||
"host": "localhost",
|
||||
"port":"3025"
|
||||
},
|
||||
"users" : [
|
||||
{
|
||||
"username" : "test@localhost",
|
||||
"enabled": true,
|
||||
"email" : "test@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"oauthClients" : [
|
||||
{
|
||||
"name" : "third-party",
|
||||
"enabled": true,
|
||||
"redirectUris": [
|
||||
"http://localhost:8081/app/*"
|
||||
],
|
||||
"secret": "password"
|
||||
}
|
||||
],
|
||||
"roleMappings": [
|
||||
{
|
||||
"username": "test@localhost",
|
||||
"roles": ["user"]
|
||||
}
|
||||
],
|
||||
"scopeMappings": [
|
||||
{
|
||||
"client": "third-party",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "perf-app",
|
||||
"roles": ["user"]
|
||||
}
|
||||
],
|
||||
"applications": [
|
||||
{
|
||||
"name": "perf-app",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8081/perf-app",
|
||||
"redirectUris": [
|
||||
"http://localhost:8081/perf-app/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8081/perf-app/perf-servlet",
|
||||
"secret": "password"
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name": "user",
|
||||
"description": "Have User privileges"
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Have Administrator privileges"
|
||||
}
|
||||
],
|
||||
"application" : {
|
||||
"perf-app" : [
|
||||
{
|
||||
"name": "customer-user",
|
||||
"description": "Have Customer User privileges"
|
||||
},
|
||||
{
|
||||
"name": "customer-admin",
|
||||
"description": "Have Customer Admin privileges"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
"applicationRoleMappings": {
|
||||
"perf-app": [
|
||||
{
|
||||
"username": "test@localhost",
|
||||
"roles": ["customer-user"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"applicationScopeMappings": {
|
||||
"perf-app": [
|
||||
{
|
||||
"client": "third-party",
|
||||
"roles": ["customer-user"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
20
testsuite/performance-web/src/test/jmeter/jmeter.properties
Normal file
20
testsuite/performance-web/src/test/jmeter/jmeter.properties
Normal 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
|
|
@ -0,0 +1,387 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jmeterTestPlan version="1.2" properties="2.6" jmeter="2.11 r1554548">
|
||||
<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>
|
||||
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="host" elementType="Argument">
|
||||
<stringProp name="Argument.name">host</stringProp>
|
||||
<stringProp name="Argument.value">${__P(host, localhost)}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="port" elementType="Argument">
|
||||
<stringProp name="Argument.name">port</stringProp>
|
||||
<stringProp name="Argument.value">${__P(port, 8081)}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="userPrefix" elementType="Argument">
|
||||
<stringProp name="Argument.name">userPrefix</stringProp>
|
||||
<stringProp name="Argument.value">${__P(userPrefix, user)}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="concurrentUsers" elementType="Argument">
|
||||
<stringProp name="Argument.name">concurrentUsers</stringProp>
|
||||
<stringProp name="Argument.value">${__P(concurrentUsers, 50)}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="iterationsPerUser" elementType="Argument">
|
||||
<stringProp name="Argument.name">iterationsPerUser</stringProp>
|
||||
<stringProp name="Argument.value">${__P(iterationsPerUser, 50)}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="refreshTokenRequestsPerIteration" elementType="Argument">
|
||||
<stringProp name="Argument.name">refreshTokenRequestsPerIteration</stringProp>
|
||||
<stringProp name="Argument.value">${__P(refreshTokenRequestsPerIteration, 2)}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</Arguments>
|
||||
<hashTree/>
|
||||
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
|
||||
<collectionProp name="CookieManager.cookies"/>
|
||||
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
|
||||
<stringProp name="CookieManager.implementation">org.apache.jmeter.protocol.http.control.HC4CookieHandler</stringProp>
|
||||
</CookieManager>
|
||||
<hashTree/>
|
||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Keycloak test" 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">${iterationsPerUser}</stringProp>
|
||||
</elementProp>
|
||||
<stringProp name="ThreadGroup.num_threads">${concurrentUsers}</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">${__javaScript(${concurrentUsers} / 5)}</stringProp>
|
||||
<longProp name="ThreadGroup.start_time">1403769177000</longProp>
|
||||
<longProp name="ThreadGroup.end_time">1403769177000</longProp>
|
||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
||||
<stringProp name="ThreadGroup.duration"></stringProp>
|
||||
<stringProp name="ThreadGroup.delay"></stringProp>
|
||||
</ThreadGroup>
|
||||
<hashTree>
|
||||
<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="User Parameters" enabled="true">
|
||||
<collectionProp name="UserParameters.names">
|
||||
<stringProp name="-265713450">username</stringProp>
|
||||
</collectionProp>
|
||||
<collectionProp name="UserParameters.thread_values">
|
||||
<collectionProp name="-1547483968">
|
||||
<stringProp name="1649970578">user-${__javaScript(${__threadNum}-1)}</stringProp>
|
||||
</collectionProp>
|
||||
</collectionProp>
|
||||
<boolProp name="UserParameters.per_iteration">false</boolProp>
|
||||
</UserParameters>
|
||||
<hashTree/>
|
||||
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain">${host}</stringProp>
|
||||
<stringProp name="HTTPSampler.port">${port}</stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path"></stringProp>
|
||||
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
|
||||
</ConfigTestElement>
|
||||
<hashTree/>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Check 200 response" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings">
|
||||
<stringProp name="49586">200</stringProp>
|
||||
</collectionProp>
|
||||
<stringProp name="TestPlan.comments">Check that status is 200 in each HTTP response</stringProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">8</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Login request" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="action" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">code</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">action</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert KC login page" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings">
|
||||
<stringProp name="-2012386511">Log in to perf-realm</stringProp>
|
||||
</collectionProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">2</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Confirm Login request" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="username" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">${username}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">username</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="password" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">password</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">password</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/auth/realms/perf-realm/tokens/auth/request/login?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fperf-app%2Fperf-servlet&state=123&client_id=perf-app</stringProp>
|
||||
<stringProp name="HTTPSampler.method">POST</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert code retrieved" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings">
|
||||
<stringProp name="726495917">RequestAction=Code retrieved</stringProp>
|
||||
</collectionProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">2</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true">
|
||||
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
|
||||
<stringProp name="RegexExtractor.refname">code</stringProp>
|
||||
<stringProp name="RegexExtractor.regex">Code=([\w\.]+)</stringProp>
|
||||
<stringProp name="RegexExtractor.template">$1$</stringProp>
|
||||
<stringProp name="RegexExtractor.default"></stringProp>
|
||||
<stringProp name="RegexExtractor.match_number">1</stringProp>
|
||||
</RegexExtractor>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Exchange code request" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet?action=exchangeCode</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token retrieved" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings">
|
||||
<stringProp name="-1001303095">RequestAction=Token retrieved</stringProp>
|
||||
<stringProp name="1027121929">Username=${username}</stringProp>
|
||||
</collectionProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">16</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
<LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
|
||||
<boolProp name="LoopController.continue_forever">true</boolProp>
|
||||
<stringProp name="LoopController.loops">${refreshTokenRequestsPerIteration}</stringProp>
|
||||
</LoopController>
|
||||
<hashTree>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="RefreshToken request" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet?action=refresh</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token refreshed" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings">
|
||||
<stringProp name="-544743205">RequestAction=Token refreshed</stringProp>
|
||||
</collectionProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">16</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Logout request" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet?action=logout</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert logged out" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings">
|
||||
<stringProp name="275024728">RequestAction=</stringProp>
|
||||
<stringProp name="-1938181625">Username=</stringProp>
|
||||
</collectionProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">20</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>false</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>false</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
</value>
|
||||
</objProp>
|
||||
<objProp>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>false</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</jmeterTestPlan>
|
|
@ -1,19 +1,16 @@
|
|||
Configuration of performance test
|
||||
=================================
|
||||
- At this moment it's src/test/jmeter/keycloak_perf_test.jmx where you can configure among other things:
|
||||
-- "ThreadGroup.num_threads" -- number of worker threads
|
||||
-- "LoopController.loops" -- Number of loops per each thread.
|
||||
|
||||
|
||||
- src/test/jmeter/system.properties -- System properties including configuration of providers. Allow to specify:
|
||||
-- Number of worker threads and loops to be used by JMeter performance test
|
||||
-- which model to use
|
||||
-- which test to run
|
||||
-- configuration of individual tests. Properties for each test documented in the file
|
||||
|
||||
Running performance tests
|
||||
=========================
|
||||
cd KEYCLOAK_HOME/testsuite
|
||||
mvn clean install -DskipTests=true -Pperformance-tests
|
||||
cd KEYCLOAK_HOME/testsuite/performance
|
||||
mvn clean verify -DskipTests=true -Pperformance-tests
|
||||
|
||||
Results:
|
||||
- Log is in: testsuite/performance/target/jmeter/logs/keycloak_perf_test.jmx.log
|
||||
|
@ -22,18 +19,26 @@ Results:
|
|||
|
||||
Example for running test
|
||||
========================
|
||||
Run:
|
||||
mvn clean install -DskipTests=true -Pperformance-tests
|
||||
1) Run:
|
||||
mvn clean verify -DskipTests=true -Pperformance-tests
|
||||
with OOTB configuration (Assumption is mongo running on 27017 as it's using mongo by default). This will create 10 new realms.
|
||||
|
||||
Then change keycloak_perf_test.jmx to have
|
||||
"ThreadGroup.num_threads" to 10 and
|
||||
"LoopController.loops" to 100
|
||||
And change "keycloak.perf.workerClass" to "org.keycloak.testsuite.performance.CreateUsersWorker" in system.properties
|
||||
2) Then change src/test/jmeter/system.properties to have
|
||||
"Tkeycloak.jmeter.numThreads" to 10 and
|
||||
"keycloak.jmeter.loops" to 100
|
||||
"keycloak.perf.workerClass" to "org.keycloak.testsuite.performance.CreateUsersWorker"
|
||||
|
||||
Then run again:
|
||||
mvn clean install -DskipTests=true -Pperformance-tests
|
||||
This will create 1000 new users (10 worker threads and each worker doing 100 iterations. Each worker is creating users in separate realm)
|
||||
mvn clean verify -DskipTests=true -Pperformance-tests
|
||||
This will create 1000 new users (10 worker threads and each worker doing 100 iterations. Each worker is creating users in separate realm. So 100 users like "user1", "user2", ... "user100" in each realm)
|
||||
|
||||
3) Then change src/test/jmeter/system.properties to have
|
||||
"keycloak.perf.workerClass" to "org.keycloak.testsuite.performance.ReadUsersWorker"
|
||||
|
||||
Then run again:
|
||||
mvn clean verify -DskipTests=true -Pperformance-tests
|
||||
This will read all 1000 previously created users and each user is read 5 times. There are 1000 iterations in total and each iteration is doing 5 read users.
|
||||
|
||||
|
||||
TODO: Easier configuration without need to edit config files, more user friendly, easier to configure and run test
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<artifactId>keycloak-testsuite-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-4-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -73,9 +73,31 @@
|
|||
<groupId>net.iharder</groupId>
|
||||
<artifactId>base64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_java</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcmail-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcmail-jdk15</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-parsers</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
@ -161,6 +183,10 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -168,6 +194,16 @@
|
|||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
|
|
|
@ -3,9 +3,12 @@ package org.keycloak.testsuite.performance;
|
|||
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.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
@ -22,7 +25,34 @@ public class BaseJMeterPerformanceTest extends AbstractJavaSamplerClient {
|
|||
|
||||
@Override
|
||||
public KeycloakSessionFactory call() throws Exception {
|
||||
return KeycloakApplication.createSessionFactory();
|
||||
KeycloakSessionFactory factory = KeycloakApplication.createSessionFactory();
|
||||
|
||||
// TODO: Workaround due to bouncycastle classpath issues. Should be fixed properly
|
||||
// new ApplianceBootstrap().bootstrap(factory, "/auth");
|
||||
bootstrapAdminRealm(factory, "/auth");
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
private void bootstrapAdminRealm(KeycloakSessionFactory factory, String contextPath) {
|
||||
KeycloakSession keycloakSession = factory.create();
|
||||
keycloakSession.getTransaction().begin();
|
||||
|
||||
try {
|
||||
String adminRealmName = Config.getAdminRealm();
|
||||
if (keycloakSession.getRealm(adminRealmName) == null) {
|
||||
|
||||
RealmManager manager = new RealmManager(keycloakSession);
|
||||
manager.setContextPath(contextPath);
|
||||
RealmModel realm = manager.createRealm(adminRealmName, adminRealmName);
|
||||
realm.setName(adminRealmName);
|
||||
realm.setEnabled(true);
|
||||
}
|
||||
|
||||
keycloakSession.getTransaction().commit();
|
||||
} finally {
|
||||
keycloakSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -42,6 +72,7 @@ public class BaseJMeterPerformanceTest extends AbstractJavaSamplerClient {
|
|||
worker = getWorker();
|
||||
|
||||
factory = getFactory();
|
||||
getLogger().info("Retrieved factory: " + factory);
|
||||
KeycloakSession session = factory.create();
|
||||
KeycloakTransaction transaction = session.getTransaction();
|
||||
transaction.begin();
|
||||
|
|
|
@ -96,8 +96,8 @@ public class ReadUsersWorker implements Worker {
|
|||
|
||||
// Read scopes of user in realm
|
||||
if (readScopes) {
|
||||
ClientModel client = realm.findClient(username);
|
||||
client.getScopeMappings();
|
||||
// ClientModel client = realm.findClient(username);
|
||||
// client.getScopeMappings();
|
||||
}
|
||||
|
||||
// Validate password (shoould be same as username)
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<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>
|
||||
<stringProp name="LoopController.loops">${__P(keycloak.jmeter.loops,10)}</stringProp>
|
||||
</elementProp>
|
||||
<stringProp name="ThreadGroup.num_threads">1</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">0</stringProp>
|
||||
<stringProp name="ThreadGroup.num_threads">${__P(keycloak.jmeter.numThreads,1)}</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">${__P(keycloak.jmeter.rampTime,0)}</stringProp>
|
||||
<longProp name="ThreadGroup.start_time">1362689985000</longProp>
|
||||
<longProp name="ThreadGroup.end_time">1362689985000</longProp>
|
||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# Thread variables used by JMeter. NumThreads is number of worker threads. Loops is number of iterations per each worker. Total number of tests is numThreads*loops.
|
||||
# reampTime is pause between startup of individual threads
|
||||
keycloak.jmeter.numThreads=1
|
||||
keycloak.jmeter.loops=10
|
||||
keycloak.jmeter.rampTime=0
|
||||
|
||||
## Choose implementation of KeycloakSessionFactory
|
||||
# keycloak.model.provider=jpa
|
||||
keycloak.model.provider=mongo
|
||||
|
@ -25,7 +31,7 @@ keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateRealmsWorker
|
|||
## 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"
|
||||
# For example: if offset==1 and in JMeter properties we have keycloak.jmeter.loops=10 and keycloak.jmeter.numThreads=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"
|
||||
|
@ -65,8 +71,8 @@ keycloak.perf.createUsers.socialLinksPerUserCount=0
|
|||
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
|
||||
# Number of users to read in each realm. After reading all 100 users, reading will start again from user1
|
||||
keycloak.perf.readUsers.countOfUsersPerRealm=100
|
||||
keycloak.perf.readUsers.readRoles=true
|
||||
keycloak.perf.readUsers.readScopes=true
|
||||
keycloak.perf.readUsers.readPassword=true
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<module>integration</module>
|
||||
<module>performance</module>
|
||||
<module>tools</module>
|
||||
<module>performance-web</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -296,6 +296,12 @@
|
|||
<build>
|
||||
<finalName>keycloak-tools</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<attachClasses>true</attachClasses>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
|
|
|
@ -17,9 +17,12 @@ import javax.ws.rs.QueryParam;
|
|||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -99,6 +102,33 @@ public class PerfTools {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@GET
|
||||
@Path("{realm}/get-users-count")
|
||||
public Response getUsersCountReq(@PathParam("realm") String realmName, @QueryParam("prefix") String prefix) {
|
||||
int usersCount = getUsersCount(realmName, prefix);
|
||||
return Response.ok(String.valueOf(usersCount)).build();
|
||||
}
|
||||
|
||||
// Same as createUsers, but dynamically compute "start" (Next available user)
|
||||
@GET
|
||||
@Path("{realm}/create-available-users")
|
||||
public void createAvailableUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count, @QueryParam("batch") Integer batch, @QueryParam("prefix") String prefix, @QueryParam("roles") String roles) throws InterruptedException {
|
||||
int start = getUsersCount(realmName, prefix);
|
||||
createUsers(realmName, count, batch, start, prefix, roles);
|
||||
}
|
||||
|
||||
private int getUsersCount(String realmName, String prefix) {
|
||||
RealmModel realm = session.getRealmByName(realmName);
|
||||
|
||||
// TODO: method for count on model
|
||||
if (prefix == null) {
|
||||
return realm.getUsers().size();
|
||||
} else {
|
||||
return realm.searchForUser(prefix).size();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("export")
|
||||
public void export(@QueryParam("dir") String dir) {
|
||||
|
|
Loading…
Reference in a new issue