diff --git a/testsuite/performance/README.md b/testsuite/performance/README.md index 67f01bf5a6..dd97d7abdd 100644 --- a/testsuite/performance/README.md +++ b/testsuite/performance/README.md @@ -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=` + +##### 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=` + ## 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. diff --git a/testsuite/performance/tests/parameters/provisioning/docker-compose/2cpus/singlenode.properties b/testsuite/performance/tests/parameters/provisioning/docker-compose/2cpus/singlenode.properties new file mode 100644 index 0000000000..4bd870874b --- /dev/null +++ b/testsuite/performance/tests/parameters/provisioning/docker-compose/2cpus/singlenode.properties @@ -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 diff --git a/testsuite/performance/tests/parameters/test/basic-oidc.properties b/testsuite/performance/tests/parameters/test/oidc-login-logout.properties similarity index 72% rename from testsuite/performance/tests/parameters/test/basic-oidc.properties rename to testsuite/performance/tests/parameters/test/oidc-login-logout.properties index c9a4af0d98..fb928793fa 100644 --- a/testsuite/performance/tests/parameters/test/basic-oidc.properties +++ b/testsuite/performance/tests/parameters/test/oidc-login-logout.properties @@ -1,4 +1,4 @@ -gatling.simulationClass=keycloak.BasicOIDCSimulation +gatling.simulationClass=keycloak.OIDCLoginAndLogoutSimulation usersPerSec=1.0 rampUpPeriod=15 warmUpPeriod=15 diff --git a/testsuite/performance/tests/parameters/test/oidc-register-logout.properties b/testsuite/performance/tests/parameters/test/oidc-register-logout.properties new file mode 100644 index 0000000000..38354c7b7a --- /dev/null +++ b/testsuite/performance/tests/parameters/test/oidc-register-logout.properties @@ -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 diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml index e7c70c5eae..5c29acc42a 100644 --- a/testsuite/performance/tests/pom.xml +++ b/testsuite/performance/tests/pom.xml @@ -35,7 +35,7 @@ ${provisioner}/4cpus/${deployment} 2u2c - basic-oidc + oidc-login-logout ${project.basedir}/parameters/provisioning/${provisioning.properties}.properties ${project.basedir}/parameters/datasets/${dataset}.properties @@ -57,7 +57,7 @@ 3.2.2 3.3.0.Final - keycloak.BasicOIDCSimulation + keycloak.OIDCLoginAndLogoutSimulation true diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmConfig.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmConfig.java index 379d74a5ef..479330b923 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmConfig.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmConfig.java @@ -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 requiredCredentials = Collections.unmodifiableList(Arrays.asList("password")); } diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationBuilder.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationBuilder.java index 026fb20bc1..009487930c 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationBuilder.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationBuilder.java @@ -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"; + } + diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java index 6d2f19461d..1aef0c9e08 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java @@ -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> clientsIteratorMap = new ConcurrentHashMap<>(); + public static Iterator getRealmsIterator() { + return sequentialRealms ? sequentialRealmsIterator() : randomRealmsIterator(); + } + public static Iterator getUsersIterator(String realm) { - return usersIteratorMap.computeIfAbsent(realm, (k) -> randomUsersIterator(realm)); + return usersIteratorMap.computeIfAbsent(realm, (k) -> sequentialUsers ? sequentialUsersIterator(realm) : randomUsersIterator(realm)); } public static Iterator 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 sequentialUsersIterator(final String realm) { return new Iterator() { - 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 sequentialRealmsIterator() { + + return new Iterator() { + + 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 randomRealmsIterator() { return new Iterator() { @@ -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)."); + } } } diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/UserInfo.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/UserInfo.java index 71e80d74ea..9538bd5d42 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/UserInfo.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/UserInfo.java @@ -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; } } diff --git a/testsuite/performance/tests/src/test/scala/Engine.scala b/testsuite/performance/tests/src/test/scala/Engine.scala index c55af4487d..ae9aaecc9a 100644 --- a/testsuite/performance/tests/src/test/scala/Engine.scala +++ b/testsuite/performance/tests/src/test/scala/Engine.scala @@ -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 diff --git a/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleSimulation.scala b/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleSimulation.scala index ac59323533..485465bb55 100644 --- a/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleSimulation.scala +++ b/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleSimulation.scala @@ -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 Marko Strukelj */ @@ -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)) } diff --git a/testsuite/performance/tests/src/test/scala/keycloak/BasicOIDCSimulation.scala b/testsuite/performance/tests/src/test/scala/keycloak/BasicOIDCSimulation.scala deleted file mode 100644 index 250ae921f3..0000000000 --- a/testsuite/performance/tests/src/test/scala/keycloak/BasicOIDCSimulation.scala +++ /dev/null @@ -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) -// } - -} diff --git a/testsuite/performance/tests/src/test/scala/keycloak/CommonScenarioBuilder.scala b/testsuite/performance/tests/src/test/scala/keycloak/CommonScenarioBuilder.scala deleted file mode 100644 index c4b9c87dff..0000000000 --- a/testsuite/performance/tests/src/test/scala/keycloak/CommonScenarioBuilder.scala +++ /dev/null @@ -1,13 +0,0 @@ -package keycloak - -import io.gatling.core.Predef._ -import io.gatling.http.Predef._ -import org.keycloak.gatling.Predef._ - -/** - * @author Tomas Kyjovsky - */ -object CommonScenarioBuilder { - -} - diff --git a/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala b/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala index 2afa96477d..ded6c150a1 100644 --- a/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala +++ b/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala @@ -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 @@ -24,7 +25,12 @@ abstract class CommonSimulation extends Simulation { println() 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( diff --git a/testsuite/performance/tests/src/test/scala/keycloak/OIDCLoginAndLogoutSimulation.scala b/testsuite/performance/tests/src/test/scala/keycloak/OIDCLoginAndLogoutSimulation.scala new file mode 100644 index 0000000000..e284897fce --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/keycloak/OIDCLoginAndLogoutSimulation.scala @@ -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)) + +} diff --git a/testsuite/performance/tests/src/test/scala/keycloak/OIDCRegisterAndLogoutSimulation.scala b/testsuite/performance/tests/src/test/scala/keycloak/OIDCRegisterAndLogoutSimulation.scala new file mode 100644 index 0000000000..f050f46d7b --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/keycloak/OIDCRegisterAndLogoutSimulation.scala @@ -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)) + +} diff --git a/testsuite/performance/tests/src/test/scala/keycloak/BasicOIDCScenarioBuilder.scala b/testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala similarity index 65% rename from testsuite/performance/tests/src/test/scala/keycloak/BasicOIDCScenarioBuilder.scala rename to testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala index d40da72a71..d868a89807 100644 --- a/testsuite/performance/tests/src/test/scala/keycloak/BasicOIDCScenarioBuilder.scala +++ b/testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala @@ -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 Marko Strukelj */ -object BasicOIDCScenarioBuilder { +object OIDCScenarioBuilder { val BASE_URL = "${keycloakServer}/realms/${realm}" val LOGIN_ENDPOINT = BASE_URL + "/protocol/openid-connect/auth" @@ -44,17 +44,49 @@ 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)