Merge pull request #5086 from tkyjovsk/KEYCLOAK-6827
KEYCLOAK-6827 Performance test for user registration
This commit is contained in:
commit
e4e14cd9f4
17 changed files with 305 additions and 121 deletions
|
@ -168,22 +168,29 @@ with `server.version` defaulting to `${project.version}` from `pom.xml`.
|
|||
|
||||
### Run Tests
|
||||
|
||||
Usage: `mvn verify -P test [-Dtest.properties=NAMED_PROPERTY_SET]`. Default property set is `basic-oidc`.
|
||||
Usage: `mvn verify -P test [-Dtest.properties=NAMED_PROPERTY_SET]`. Default property set is `oidc-login-logout`.
|
||||
|
||||
The parameters are loaded from `tests/parameters/test/${test.properties}.properties` file.
|
||||
Individual properties can be overriden from command line via `-D` params.
|
||||
|
||||
To use a custom properties file specify `-Dtest.properties.file=ABSOLUTE_PATH_TO_FILE` instead of `-Dtest.properties`.
|
||||
|
||||
When running the tests it is also necessary to define a dataset to use. Usage is described in the section above.
|
||||
#### Dataset
|
||||
|
||||
When running the tests it is necessary to define the dataset to be used.
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `dataset` | Name of the dataset to use. Individual parameters can be overriden from CLI. For details see the section above. | `default` |
|
||||
| `sequentialRealmsFrom` | Use sequential realm iteration starting from specific index. Must be lower than `numOfRealms` parameter from dataset properties. Useful for user registration scenario. | `-1` random iteration |
|
||||
| `sequentialUsersFrom` | Use sequential user iteration starting from specific index. Must be lower than `usersPerRealm` parameter from dataset properties. Useful for user registration scenario. | `-1` random iteration |
|
||||
|
||||
#### Common Test Run Parameters
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `gatling.simulationClass` | Classname of the simulation to be run. | `keycloak.BasicOIDCSimulation` |
|
||||
| `dataset` | Name of the dataset to use. (Individual dataset properties can be overridden with `-Ddataset.property=value`.) | `default` |
|
||||
| `usersPerSec` | Arrival rate of new users per second. Can be a floating point number. | `1.0` for BasicOIDCSimulation, `0.2` for AdminConsoleSimulation |
|
||||
| `gatling.simulationClass` | Classname of the simulation to be run. | `keycloak.OIDCLoginAndLogoutSimulation` |
|
||||
| `usersPerSec` | Arrival rate of new users per second. Can be a floating point number. | `1.0` for OIDCLoginAndLogoutSimulation, `0.2` for AdminConsoleSimulation |
|
||||
| `rampUpPeriod` | Period during which the users will be ramped up. (seconds) | `15` |
|
||||
| `warmUpPeriod` | Period with steady number of users intended for the system under test to warm up. (seconds) | `15` |
|
||||
| `measurementPeriod` | A measurement period after the system is warmed up. (seconds) | `30` |
|
||||
|
@ -191,7 +198,7 @@ When running the tests it is also necessary to define a dataset to use. Usage is
|
|||
| `userThinkTime` | Pause between individual scenario steps. | `5` |
|
||||
| `refreshTokenPeriod`| Period after which token should be refreshed. | `10` |
|
||||
|
||||
#### Test Run Parameters specific to `BasicOIDCSimulation`
|
||||
#### Test Run Parameters specific to `OIDCLoginAndLogoutSimulation`
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
|
@ -206,12 +213,30 @@ When running the tests it is also necessary to define a dataset to use. Usage is
|
|||
|
||||
- Run test specific test and dataset parameters:
|
||||
|
||||
`mvn verify -P test -Dtest.properties=basic-oidc -Ddataset=100u2c`
|
||||
`mvn verify -P test -Dtest.properties=oidc-login-logout -Ddataset=100u2c`
|
||||
|
||||
- Run test with specific test and dataset parameters, overriding some from command line:
|
||||
|
||||
`mvn verify -P test -Dtest.properties=admin-console -Ddataset=100u2c -DrampUpPeriod=30 -DwarmUpPeriod=60 -DusersPerSec=0.3`
|
||||
|
||||
#### Running `OIDCRegisterAndLogoutSimulation`
|
||||
|
||||
Running the user registration simulation requires a different approach to dataset and how it's iterated.
|
||||
- It requires sequential iteration instead of the default random one.
|
||||
- In case some users are already registered it requires starting the iteration from a specific index .
|
||||
|
||||
##### Example A:
|
||||
1. Generate dataset with 0 users: `mvn verify -P generate-data -DusersPerRealm=0`
|
||||
2. Run the registration test:
|
||||
|
||||
`mvn verify -P test -D test.properties=oidc-register-logout -DsequentialUsersFrom=0 -DusersPerRealm=<MAX_EXPECTED_REGISTRATIONS>`
|
||||
|
||||
##### Example B:
|
||||
1. Generate or import dataset with 100 users: `mvn verify -P generate-data -Ddataset=100u2c`. This will create 1 realm and users 0-99.
|
||||
2. Run the registration test starting from user 100:
|
||||
|
||||
`mvn verify -P test -D test.properties=oidc-register-logout -DsequentialUsersFrom=100 -DusersPerRealm=<MAX_EXPECTED_REGISTRATIONS>`
|
||||
|
||||
|
||||
## Monitoring
|
||||
|
||||
|
@ -271,7 +296,7 @@ Thus, it's best to download and install [this SDK version](http://scala-lang.org
|
|||
Open Preferences in IntelliJ. Type 'plugins' in the search box. In the right pane click on 'Install JetBrains plugin'.
|
||||
Type 'scala' in the search box, and click Install button of the Scala plugin.
|
||||
|
||||
#### Run BasicOIDCSimulation from IntelliJ
|
||||
#### Run OIDCLoginAndLogoutSimulation from IntelliJ
|
||||
|
||||
Make sure that `performance` maven profile is enabled for IDEA to treat `performance` directory as a project module.
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#provisioner=docker-compose
|
||||
#deployment=singlenode
|
||||
|
||||
# Keycloak Settings
|
||||
keycloak.scale=1
|
||||
keycloak.docker.cpusets=1
|
||||
keycloak.docker.memlimit=2500m
|
||||
keycloak.jvm.memory=-Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||
keycloak.http.max-connections=50000
|
||||
keycloak.worker.io-threads=2
|
||||
keycloak.worker.task-max-threads=16
|
||||
keycloak.ds.min-pool-size=10
|
||||
keycloak.ds.max-pool-size=100
|
||||
keycloak.ds.pool-prefill=true
|
||||
keycloak.ds.ps-cache-size=100
|
||||
|
||||
# Database Settings
|
||||
db.docker.cpusets=0
|
||||
db.docker.memlimit=2g
|
||||
db.max.connections=100
|
||||
|
||||
# Monitoring Settings
|
||||
monitoring.docker.cpusets=0
|
|
@ -1,4 +1,4 @@
|
|||
gatling.simulationClass=keycloak.BasicOIDCSimulation
|
||||
gatling.simulationClass=keycloak.OIDCLoginAndLogoutSimulation
|
||||
usersPerSec=1.0
|
||||
rampUpPeriod=15
|
||||
warmUpPeriod=15
|
|
@ -0,0 +1,10 @@
|
|||
gatling.simulationClass=keycloak.OIDCRegisterAndLogoutSimulation
|
||||
usersPerSec=1.0
|
||||
rampUpPeriod=15
|
||||
warmUpPeriod=15
|
||||
measurementPeriod=30
|
||||
filterResults=false
|
||||
userThinkTime=0
|
||||
refreshTokenPeriod=0
|
||||
refreshTokenCount=1
|
||||
badLoginAttempts=1
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<provisioning.properties>${provisioner}/4cpus/${deployment}</provisioning.properties>
|
||||
<dataset>2u2c</dataset>
|
||||
<test.properties>basic-oidc</test.properties>
|
||||
<test.properties>oidc-login-logout</test.properties>
|
||||
|
||||
<provisioning.properties.file>${project.basedir}/parameters/provisioning/${provisioning.properties}.properties</provisioning.properties.file>
|
||||
<dataset.properties.file>${project.basedir}/parameters/datasets/${dataset}.properties</dataset.properties.file>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>
|
||||
<jboss-logging.version>3.3.0.Final</jboss-logging.version>
|
||||
|
||||
<gatling.simulationClass>keycloak.BasicOIDCSimulation</gatling.simulationClass>
|
||||
<gatling.simulationClass>keycloak.OIDCLoginAndLogoutSimulation</gatling.simulationClass>
|
||||
<gatling.skip.run>true</gatling.skip.run>
|
||||
</properties>
|
||||
|
||||
|
|
|
@ -9,6 +9,6 @@ import java.util.List;
|
|||
*/
|
||||
public class RealmConfig {
|
||||
public int accessTokenLifeSpan = 60;
|
||||
public boolean registrationAllowed = false;
|
||||
public boolean registrationAllowed = true;
|
||||
public List<String> requiredCredentials = Collections.unmodifiableList(Arrays.asList("password"));
|
||||
}
|
||||
|
|
|
@ -120,9 +120,9 @@ public class RealmsConfigurationBuilder {
|
|||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername(computeUsername(realmName, j));
|
||||
user.setEnabled(true);
|
||||
user.setEmail(user.getUsername() + "@example.com");
|
||||
user.setFirstName("User" + j);
|
||||
user.setLastName("O'realm" + i);
|
||||
user.setEmail(computeEmail(user.getUsername()));
|
||||
user.setFirstName(computeFirstName(j));
|
||||
user.setLastName(computeLastName(realmName));
|
||||
|
||||
CredentialRepresentation creds = new CredentialRepresentation();
|
||||
creds.setType("password");
|
||||
|
@ -309,6 +309,18 @@ public class RealmsConfigurationBuilder {
|
|||
return "passOfUser_" + username;
|
||||
}
|
||||
|
||||
static String computeFirstName(int userIdx) {
|
||||
return "User" + userIdx;
|
||||
}
|
||||
|
||||
static String computeLastName(String realm) {
|
||||
return "O'" + realm.replace("_", "");
|
||||
}
|
||||
|
||||
static String computeEmail(String username) {
|
||||
return username + "@example.com";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeAppUrl;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeClientId;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeEmail;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeFirstName;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeLastName;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computePassword;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeSecret;
|
||||
import static org.keycloak.performance.RealmsConfigurationBuilder.computeUsername;
|
||||
|
@ -51,6 +54,12 @@ public class TestConfig {
|
|||
public static final int clientRolesPerUser = Integer.getInteger("clientRolesPerUser", 2);
|
||||
public static final int clientRolesPerClient = Integer.getInteger("clientRolesPerClient", 2);
|
||||
|
||||
// sequential vs random dataset iteration
|
||||
public static final int sequentialRealmsFrom = Integer.getInteger("sequentialRealmsFrom", -1); // -1 means random iteration
|
||||
public static final int sequentialUsersFrom = Integer.getInteger("sequentialUsersFrom", -1); // -1 means random iteration
|
||||
public static final boolean sequentialRealms = sequentialRealmsFrom >= 0;
|
||||
public static final boolean sequentialUsers = sequentialUsersFrom >= 0;
|
||||
|
||||
//
|
||||
// Settings used by tests to control common test parameters
|
||||
//
|
||||
|
@ -69,7 +78,7 @@ public class TestConfig {
|
|||
public static final long measurementEndTime = measurementStartTime + measurementPeriod * 1000;
|
||||
|
||||
//
|
||||
// Settings used by BasicOIDCSimulation to control behavior specific to BasicOIDCSimulation
|
||||
// Settings used by OIDCLoginAndLogoutSimulation to control behavior specific to OIDCLoginAndLogoutSimulation
|
||||
//
|
||||
public static final int badLoginAttempts = Integer.getInteger("badLoginAttempts", 0);
|
||||
public static final int refreshTokenCount = Integer.getInteger("refreshTokenCount", 0);
|
||||
|
@ -101,8 +110,12 @@ public class TestConfig {
|
|||
// Clients iterators by realm
|
||||
private static final ConcurrentMap<String, Iterator<ClientInfo>> clientsIteratorMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static Iterator<String> getRealmsIterator() {
|
||||
return sequentialRealms ? sequentialRealmsIterator() : randomRealmsIterator();
|
||||
}
|
||||
|
||||
public static Iterator<UserInfo> getUsersIterator(String realm) {
|
||||
return usersIteratorMap.computeIfAbsent(realm, (k) -> randomUsersIterator(realm));
|
||||
return usersIteratorMap.computeIfAbsent(realm, (k) -> sequentialUsers ? sequentialUsersIterator(realm) : randomUsersIterator(realm));
|
||||
}
|
||||
|
||||
public static Iterator<ClientInfo> getClientsIterator(String realm) {
|
||||
|
@ -140,15 +153,30 @@ public class TestConfig {
|
|||
}
|
||||
|
||||
public static String toStringDatasetProperties() {
|
||||
return String.format(" numOfRealms: %s\n usersPerRealm: %s\n clientsPerRealm: %s\n realmRoles: %s\n realmRolesPerUser: %s\n clientRolesPerUser: %s\n clientRolesPerClient: %s\n hashIterations: %s",
|
||||
numOfRealms, usersPerRealm, clientsPerRealm, realmRoles, realmRolesPerUser, clientRolesPerUser, clientRolesPerClient, hashIterations);
|
||||
return String.format(
|
||||
" numOfRealms: %s%s\n"
|
||||
+ " usersPerRealm: %s%s\n"
|
||||
+ " clientsPerRealm: %s\n"
|
||||
+ " realmRoles: %s\n"
|
||||
+ " realmRolesPerUser: %s\n"
|
||||
+ " clientRolesPerUser: %s\n"
|
||||
+ " clientRolesPerClient: %s\n"
|
||||
+ " hashIterations: %s",
|
||||
numOfRealms, sequentialRealms ? ", sequential iteration starting from " + sequentialRealmsFrom: "",
|
||||
usersPerRealm, sequentialUsers ? ", sequential iteration starting from " + sequentialUsersFrom: "",
|
||||
clientsPerRealm,
|
||||
realmRoles,
|
||||
realmRolesPerUser,
|
||||
clientRolesPerUser,
|
||||
clientRolesPerClient,
|
||||
hashIterations);
|
||||
}
|
||||
|
||||
public static Iterator<UserInfo> sequentialUsersIterator(final String realm) {
|
||||
|
||||
return new Iterator<UserInfo>() {
|
||||
|
||||
int idx = 0;
|
||||
int idx = sequentialUsers ? sequentialUsersFrom : 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
@ -162,8 +190,14 @@ public class TestConfig {
|
|||
}
|
||||
|
||||
String user = computeUsername(realm, idx);
|
||||
String firstName= computeFirstName(idx);
|
||||
idx += 1;
|
||||
return new UserInfo(user, computePassword(user));
|
||||
return new UserInfo(user,
|
||||
computePassword(user),
|
||||
firstName,
|
||||
computeLastName(realm),
|
||||
computeEmail(user)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -179,8 +213,14 @@ public class TestConfig {
|
|||
|
||||
@Override
|
||||
public UserInfo next() {
|
||||
String user = computeUsername(realm, ThreadLocalRandom.current().nextInt(usersPerRealm));
|
||||
return new UserInfo(user, computePassword(user));
|
||||
int idx = ThreadLocalRandom.current().nextInt(usersPerRealm);
|
||||
String user = computeUsername(realm, idx);
|
||||
return new UserInfo(user,
|
||||
computePassword(user),
|
||||
computeFirstName(idx),
|
||||
computeLastName(realm),
|
||||
computeEmail(user)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -204,6 +244,29 @@ public class TestConfig {
|
|||
};
|
||||
}
|
||||
|
||||
public static Iterator<String> sequentialRealmsIterator() {
|
||||
|
||||
return new Iterator<String>() {
|
||||
|
||||
int idx = sequentialRealms ? sequentialRealmsFrom : 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
if (idx >= numOfRealms) {
|
||||
idx = 0;
|
||||
}
|
||||
String realm = "realm_" + idx;
|
||||
idx += 1;
|
||||
return realm;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Iterator<String> randomRealmsIterator() {
|
||||
|
||||
return new Iterator<String>() {
|
||||
|
@ -220,13 +283,19 @@ public class TestConfig {
|
|||
};
|
||||
}
|
||||
|
||||
static void validateConfiguration() {
|
||||
public static void validateConfiguration() {
|
||||
if (realmRolesPerUser > realmRoles) {
|
||||
throw new RuntimeException("Can't have more realmRolesPerUser than there are realmRoles");
|
||||
}
|
||||
if (clientRolesPerUser > clientsPerRealm * clientRolesPerClient) {
|
||||
throw new RuntimeException("Can't have more clientRolesPerUser than there are all client roles (clientsPerRealm * clientRolesPerClient)");
|
||||
}
|
||||
if (sequentialRealmsFrom < -1 || sequentialRealmsFrom >= numOfRealms) {
|
||||
throw new RuntimeException("The folowing condition must be met: (-1 <= sequentialRealmsFrom < numOfRealms).");
|
||||
}
|
||||
if (sequentialUsersFrom < -1 || sequentialUsersFrom >= usersPerRealm) {
|
||||
throw new RuntimeException("The folowing condition must be met: (-1 <= sequentialUsersFrom < usersPerRealm).");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,9 +7,15 @@ public class UserInfo {
|
|||
|
||||
public final String username;
|
||||
public final String password;
|
||||
public final String firstName;
|
||||
public final String lastName;
|
||||
public final String email;
|
||||
|
||||
UserInfo(String username, String password) {
|
||||
UserInfo(String username, String password, String firstName, String lastName, String email) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.gatling.core.config.GatlingPropertiesBuilder
|
|||
|
||||
object Engine extends App {
|
||||
|
||||
val sim = classOf[keycloak.BasicOIDCSimulation]
|
||||
val sim = classOf[keycloak.OIDCLoginAndLogoutSimulation]
|
||||
//val sim = classOf[keycloak.AdminConsoleSimulation]
|
||||
|
||||
val props = new GatlingPropertiesBuilder
|
||||
|
|
|
@ -3,11 +3,6 @@ package keycloak
|
|||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
|
||||
import keycloak.CommonScenarioBuilder._
|
||||
|
||||
import org.keycloak.performance.TestConfig
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
|
@ -80,10 +75,6 @@ class AdminConsoleSimulation extends CommonSimulation {
|
|||
|
||||
val adminScenario = scenario("AdminConsole").exec(adminSession.chainBuilder)
|
||||
|
||||
setUp(adminScenario
|
||||
.inject(
|
||||
rampUsersPerSec(0.001) to TestConfig.usersPerSec during(TestConfig.rampUpPeriod) ,
|
||||
constantUsersPerSec(TestConfig.usersPerSec) during(TestConfig.warmUpPeriod + TestConfig.measurementPeriod)
|
||||
).protocols(httpProtocol))
|
||||
setUp(adminScenario.inject(defaultInjectionProfile).protocols(httpProtocol))
|
||||
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package keycloak
|
||||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import keycloak.CommonScenarioBuilder._
|
||||
import keycloak.BasicOIDCScenarioBuilder._
|
||||
|
||||
import org.keycloak.performance.TestConfig
|
||||
|
||||
|
||||
/**
|
||||
* @author Radim Vansa <rvansa@redhat.com>
|
||||
* @author Marko Strukelj <mstrukel@redhat.com>
|
||||
*/
|
||||
class BasicOIDCSimulation extends CommonSimulation {
|
||||
|
||||
override def printSpecificTestParameters {
|
||||
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
|
||||
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
|
||||
}
|
||||
|
||||
val httpDefault = http
|
||||
.acceptHeader("application/json")
|
||||
.disableFollowRedirect
|
||||
.inferHtmlResources
|
||||
|
||||
val userSession = new BasicOIDCScenarioBuilder()
|
||||
|
||||
.browserOpensLoginPage()
|
||||
|
||||
.thinkPause()
|
||||
.browserPostsWrongCredentials()
|
||||
.browserPostsCorrectCredentials()
|
||||
|
||||
// Act as client adapter - exchange code for keys
|
||||
.adapterExchangesCodeForTokens()
|
||||
|
||||
.refreshTokenSeveralTimes()
|
||||
|
||||
.thinkPause()
|
||||
.logout()
|
||||
|
||||
.thinkPause()
|
||||
|
||||
|
||||
val usersScenario = scenario("users").exec(userSession.chainBuilder)
|
||||
|
||||
setUp(usersScenario.inject(
|
||||
rampUsersPerSec(0.001) to TestConfig.usersPerSec during(TestConfig.rampUpPeriod),
|
||||
constantUsersPerSec(TestConfig.usersPerSec) during(TestConfig.warmUpPeriod + TestConfig.measurementPeriod)
|
||||
).protocols(httpDefault))
|
||||
|
||||
// after {
|
||||
// filterResults(getClass)
|
||||
// }
|
||||
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package keycloak
|
||||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import org.keycloak.gatling.Predef._
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:tkyjovsk@redhat.com">Tomas Kyjovsky</a>
|
||||
*/
|
||||
object CommonScenarioBuilder {
|
||||
|
||||
}
|
||||
|
|
@ -2,10 +2,11 @@ package keycloak
|
|||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import keycloak.CommonScenarioBuilder._
|
||||
import org.keycloak.performance.log.LogProcessor
|
||||
import io.gatling.core.validation.Validation
|
||||
|
||||
import io.gatling.core.controller.inject.InjectionStep
|
||||
|
||||
import org.keycloak.performance.TestConfig
|
||||
|
||||
|
||||
|
@ -25,6 +26,11 @@ abstract class CommonSimulation extends Simulation {
|
|||
println("Timestamps: \n" + TestConfig.toStringTimestamps)
|
||||
println()
|
||||
|
||||
var defaultInjectionProfile = Array[InjectionStep] (
|
||||
rampUsersPerSec(0.001) to TestConfig.usersPerSec during(TestConfig.rampUpPeriod),
|
||||
constantUsersPerSec(TestConfig.usersPerSec) during(TestConfig.warmUpPeriod + TestConfig.measurementPeriod)
|
||||
)
|
||||
|
||||
def printSpecificTestParameters {
|
||||
// override in subclass
|
||||
}
|
||||
|
@ -33,6 +39,10 @@ abstract class CommonSimulation extends Simulation {
|
|||
System.currentTimeMillis < TestConfig.measurementEndTime
|
||||
}
|
||||
|
||||
before {
|
||||
TestConfig.validateConfiguration
|
||||
}
|
||||
|
||||
after {
|
||||
if (TestConfig.filterResults) {
|
||||
new LogProcessor(getClass).filterLog(
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package keycloak
|
||||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import keycloak.OIDCScenarioBuilder._
|
||||
|
||||
import org.keycloak.performance.TestConfig
|
||||
|
||||
|
||||
class OIDCLoginAndLogoutSimulation extends CommonSimulation {
|
||||
|
||||
override def printSpecificTestParameters {
|
||||
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
|
||||
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
|
||||
}
|
||||
|
||||
val usersScenario = scenario("Logging-in Users").exec(loginAndLogoutScenario.chainBuilder)
|
||||
|
||||
setUp(usersScenario.inject(defaultInjectionProfile).protocols(httpDefault))
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package keycloak
|
||||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import keycloak.OIDCScenarioBuilder._
|
||||
|
||||
import org.keycloak.performance.TestConfig
|
||||
|
||||
|
||||
class OIDCRegisterAndLogoutSimulation extends CommonSimulation {
|
||||
|
||||
override def printSpecificTestParameters {
|
||||
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
|
||||
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
|
||||
}
|
||||
|
||||
val usersScenario = scenario("Registering Users").exec(registerAndLogoutScenario.chainBuilder)
|
||||
|
||||
setUp(usersScenario.inject(defaultInjectionProfile).protocols(httpDefault))
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@ package keycloak
|
|||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import org.keycloak.gatling.Predef._
|
||||
import keycloak.BasicOIDCScenarioBuilder._
|
||||
import keycloak.OIDCScenarioBuilder._
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
|
@ -21,7 +21,7 @@ import org.keycloak.performance.TestConfig
|
|||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
object BasicOIDCScenarioBuilder {
|
||||
object OIDCScenarioBuilder {
|
||||
|
||||
val BASE_URL = "${keycloakServer}/realms/${realm}"
|
||||
val LOGIN_ENDPOINT = BASE_URL + "/protocol/openid-connect/auth"
|
||||
|
@ -45,16 +45,48 @@ object BasicOIDCScenarioBuilder {
|
|||
missCounter.getAndDecrement() > 0
|
||||
}
|
||||
|
||||
val httpDefault = http
|
||||
.acceptHeader("application/json")
|
||||
.disableFollowRedirect
|
||||
.inferHtmlResources
|
||||
|
||||
val loginAndLogoutScenario = new OIDCScenarioBuilder()
|
||||
.browserOpensLoginPage()
|
||||
.thinkPause()
|
||||
.browserPostsWrongCredentials()
|
||||
.browserPostsCorrectCredentials()
|
||||
|
||||
// Act as client adapter - exchange code for keys
|
||||
.adapterExchangesCodeForTokens()
|
||||
|
||||
.refreshTokenSeveralTimes()
|
||||
|
||||
.thinkPause()
|
||||
.logout()
|
||||
|
||||
.thinkPause()
|
||||
|
||||
val registerAndLogoutScenario = new OIDCScenarioBuilder()
|
||||
.browserOpensLoginPage()
|
||||
.thinkPause()
|
||||
.browserOpensRegistrationPage()
|
||||
.thinkPause()
|
||||
.browserPostsRegistrationDetails()
|
||||
.adapterExchangesCodeForTokens()
|
||||
.thinkPause()
|
||||
.logout()
|
||||
.thinkPause()
|
||||
|
||||
}
|
||||
|
||||
|
||||
class BasicOIDCScenarioBuilder {
|
||||
class OIDCScenarioBuilder {
|
||||
|
||||
var chainBuilder = exec(s => {
|
||||
|
||||
// initialize session with host, user, client app, login failure ratio ...
|
||||
val realm = TestConfig.randomRealmsIterator().next()
|
||||
val userInfo = TestConfig.getUsersIterator(realm).next()
|
||||
val realm = TestConfig.getRealmsIterator().next();
|
||||
val userInfo = TestConfig.getUsersIterator(realm).next();
|
||||
val clientInfo = TestConfig.getConfidentialClientsIterator(realm).next()
|
||||
|
||||
AuthorizeAction.init(s)
|
||||
|
@ -63,6 +95,9 @@ class BasicOIDCScenarioBuilder {
|
|||
"wrongPasswordCount" -> new AtomicInteger(TestConfig.badLoginAttempts),
|
||||
"refreshTokenCount" -> new AtomicInteger(TestConfig.refreshTokenCount),
|
||||
"realm" -> realm,
|
||||
"firstName" -> userInfo.firstName,
|
||||
"lastName" -> userInfo.lastName,
|
||||
"email" -> userInfo.email,
|
||||
"username" -> userInfo.username,
|
||||
"password" -> userInfo.password,
|
||||
"clientId" -> clientInfo.clientId,
|
||||
|
@ -72,7 +107,7 @@ class BasicOIDCScenarioBuilder {
|
|||
})
|
||||
.exitHereIfFailed
|
||||
|
||||
def thinkPause() : BasicOIDCScenarioBuilder = {
|
||||
def thinkPause() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
||||
this
|
||||
}
|
||||
|
@ -85,7 +120,7 @@ class BasicOIDCScenarioBuilder {
|
|||
pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
||||
}
|
||||
|
||||
def browserOpensLoginPage() : BasicOIDCScenarioBuilder = {
|
||||
def browserOpensLoginPage() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.exec(http("Browser to Log In Endpoint")
|
||||
.get(LOGIN_ENDPOINT)
|
||||
|
@ -95,7 +130,9 @@ class BasicOIDCScenarioBuilder {
|
|||
.queryParam("client_id", "${clientId}")
|
||||
.queryParam("state", "${state}")
|
||||
.queryParam("redirect_uri", "${appUrl}")
|
||||
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
||||
.check(status.is(200),
|
||||
regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri"),
|
||||
regex("href=\"/auth(/realms/[^\"]*/login-actions/registration[^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("registration-link")))
|
||||
// if already logged in the check will fail with:
|
||||
// status.find.is(200), but actually found 302
|
||||
// The reason is that instead of returning the login page we are immediately redirected to the app that requested authentication
|
||||
|
@ -103,7 +140,7 @@ class BasicOIDCScenarioBuilder {
|
|||
this
|
||||
}
|
||||
|
||||
def browserPostsWrongCredentials() : BasicOIDCScenarioBuilder = {
|
||||
def browserPostsWrongCredentials() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.asLongAs(s => downCounterAboveZero(s, "wrongPasswordCount")) {
|
||||
var c = exec(http("Browser posts wrong credentials")
|
||||
|
@ -122,7 +159,7 @@ class BasicOIDCScenarioBuilder {
|
|||
this
|
||||
}
|
||||
|
||||
def browserPostsCorrectCredentials() : BasicOIDCScenarioBuilder = {
|
||||
def browserPostsCorrectCredentials() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.exec(http("Browser posts correct credentials")
|
||||
.post("${login-form-uri}")
|
||||
|
@ -135,7 +172,36 @@ class BasicOIDCScenarioBuilder {
|
|||
this
|
||||
}
|
||||
|
||||
def adapterExchangesCodeForTokens() : BasicOIDCScenarioBuilder = {
|
||||
def browserOpensRegistrationPage() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.exec(http("Browser to Registration Endpoint")
|
||||
.get("${keycloakServer}${registration-link}")
|
||||
.headers(UI_HEADERS)
|
||||
.check(
|
||||
status.is(200),
|
||||
regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("registration-form-uri"))
|
||||
)
|
||||
.exitHereIfFailed
|
||||
this
|
||||
}
|
||||
|
||||
def browserPostsRegistrationDetails() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.exec(http("Browser posts registration details")
|
||||
.post("${registration-form-uri}")
|
||||
.headers(UI_HEADERS)
|
||||
.formParam("firstName", "${firstName}")
|
||||
.formParam("lastName", "${lastName}")
|
||||
.formParam("email", "${email}")
|
||||
.formParam("username", "${username}")
|
||||
.formParam("password", "${password}")
|
||||
.formParam("password-confirm", "${password}")
|
||||
.check(status.is(302), header("Location").saveAs("login-redirect")))
|
||||
.exitHereIfFailed
|
||||
this
|
||||
}
|
||||
|
||||
def adapterExchangesCodeForTokens() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.exec(oauth("Adapter exchanges code for tokens")
|
||||
.authorize("${login-redirect}",
|
||||
|
@ -149,7 +215,7 @@ class BasicOIDCScenarioBuilder {
|
|||
this
|
||||
}
|
||||
|
||||
def refreshTokenSeveralTimes() : BasicOIDCScenarioBuilder = {
|
||||
def refreshTokenSeveralTimes() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.asLongAs(s => downCounterAboveZero(s, "refreshTokenCount")) {
|
||||
// make sure to call newThinkPause rather than thinkPause
|
||||
|
@ -159,7 +225,7 @@ class BasicOIDCScenarioBuilder {
|
|||
this
|
||||
}
|
||||
|
||||
def logout() : BasicOIDCScenarioBuilder = {
|
||||
def logout() : OIDCScenarioBuilder = {
|
||||
chainBuilder = chainBuilder
|
||||
.exec(http("Browser logout")
|
||||
.get(LOGOUT_ENDPOINT)
|
Loading…
Reference in a new issue