diff --git a/testsuite/performance/README.datasets.md b/testsuite/performance/README.datasets.md index 2338be1bd9..08d0082d5b 100644 --- a/testsuite/performance/README.datasets.md +++ b/testsuite/performance/README.datasets.md @@ -1,46 +1,152 @@ -# Keycloak Performance Testsuite - Generating datasets +# Keycloak Datasets +## Provision Keycloak Server -## Generating a set of datasets for multiple realms - -The first dataset is small and is created quickly. Building of each subsequent dataset continues on top -of the previous dataset. - -Datasets are created with a specific released server version (rather than a snapshot) in order to be -usable with later releases - newer server version should be able to migrate schema from any previous release. - -We use 10 concurrent threads, which is enough to saturate a -dual core machine. For quad-core you can try to double the number of workers. +Before generating data it is necessary to provision/start Keycloak server. This can +be done automatically by running: ``` cd testsuite/performance - -mvn clean install -Dserver.version=4.0.0.Beta1 - -mvn verify -Pteardown -mvn verify -Pprovision -mvn verify -Pgenerate-data -Ddataset=10r100u1c -DnumOfWorkers=10 -mvn verify -Pexport-dump -Ddataset=10r100u1c - -mvn verify -Pgenerate-data -Ddataset=20r100u1c -DstartAtRealmIdx=10 -DnumOfWorkers=10 -mvn verify -Pexport-dump -Ddataset=20r100u1c - -mvn verify -Pgenerate-data -Ddataset=50r100u1c -DstartAtRealmIdx=20 -DnumOfWorkers=10 -mvn verify -Pexport-dump -Ddataset=50r100u1c - -mvn verify -Pgenerate-data -Ddataset=200r100u1c -DstartAtRealmIdx=50 -DnumOfWorkers=10 -mvn verify -Pexport-dump -Ddataset=200r100u1c - -mvn verify -Pgenerate-data -Ddataset=500r100u1c -DstartAtRealmIdx=200 -DnumOfWorkers=10 -mvn verify -Pexport-dump -Ddataset=500r100u1c -``` - -If the dataset dump file is not available locally but it's known that the dataset for specific version exists on the server -it can be retrieved by specifying a proper server version again. For example: -``` -mvn verify -Pteardown mvn clean install -mvn verify -Pprovision -mvn verify -Pimport-dump -Ddataset=20r100u1c -Dserver.version=4.0.0.Beta1 - +mvn verify -P provision ``` +To tear down the system after testing run: +``` +mvn verify -P teardown +``` +The teardown step will delete the database as well so it is possible to use it between generating different datasets. + +It is also possible to start the server externally (manually). In that case it is necessary +to provide information in file `tests/target/provisioned-system.properties`. +See the main README for details. + +## Generate Data + +To generate the *default dataset* run: +``` +cd testsuite/performance +mvn verify -P generate-data +``` + +To generate a *specific dataset* from within the project run: +``` +mvn verify -P generate-data -Ddataset= +``` +This will load dataset properties from `tests/src/test/resources/dataset/${dataset}.properties`. + +To generate a specific dataset from a *custom properties file* run: +``` +mvn verify -P generate-data -Ddataset.properties.file= +``` + +To delete a dataset run: +``` +mvn verify -P generate-data -Ddataset=… -Ddelete=true +``` +This will delete all realms specified by the dataset. + + +## Indexed Model + +The model is hierarchical with the parent-child relationships determined by primary foreign keys of entities. + +Size of the dataset is determined by specifying a "count per parent" parameter for each entity. + +Number of mappings between entities created by the primary "count per parent" parameters +can be speicied by "count per other entity" parameters. + +Each nested entity has a unique index which identifies it inside its parent entity. + +For example: +- Realm X --> Client Y --> Client Role Z +- Realm X --> Client Y --> Resource Server --> Resource Z +- Realm X --> User Y +- etc. + +Hash code of each entity is computed based on its index coordinates within the model and its class name. + +Each entity holds entity representation, and a list of mappings to other entities in the indexed model. +The attributes and mappings are initialized by a related *entity template* class. +Each entity class also acts as a wrapper around a Keycloak Admin Client using it +to provide CRUD operations for its entity. + +The `id` attribute in the entity representation is set upon entity creation, or in case +an already initialized entity was removed from LRU cache it is reloaded from the server. +This may happen if the number of entities is larger than entity cache size. (see below) + +### Attribute Templating + +Attributes of each supported entity representation can be set via FreeMarker templates. +The process is based on templates defined in a properties configuration file. + +The first template in the list can use the `index` of the entity and any attributes of its parent entity. +Each subsequent attribute template can use any previously set attribute values. + +Note: Output of FreeMarker engine is always a String. Transition to the actual type +of the attribute is done with the Jackson 2.9+ parser using `ObjectMapper.update()` method +which allows a gradual updates of an existing Java object. + +### Randomness + +Randomness in the indexed model is deterministic (pseudorandom) because the +random seeds are based on deterministic hash codes. + +There are 2 types of seeds: one is for using randoms in the FreeMarker templates +via methods `indexBasedRandomInt(int bound)` and `indexBasedRandomBool(int percentage)`. +It is based on class of the current entity + hash code of its parent entity. + +The other seed is for generating mappings to other entities which are just +random sequences of integer indexes. This is based on hash code of the current entity. + +### Generator Settings + +#### Timeouts +- `queue.timeout`: How long to wait for an entity to be processed by a thread-pool executor. Default is `60` seconds. +You might want to increase this setting when deleting many realms with many nested entities using a low number of workers. +- `shutdown.timeout`: How long to wait for the executor thread-pool to shut down. Default is `60` seconds. + +#### Caching and Memory +- `template.cache.size`: Size of cache of FreeMarker template models. Default is `10000`. +- `randoms.cache.size`: Size of cache of random integer sequences which are used for mappings between entities. Default is `10000`. +- `entity.cache.size`: Size of cache of initialized entities. Default is `100000`. +- `max.heap`: Max heap size of the data generator JVM. + + +## Notes: + +- Mappings are random so it can sometimes happen that the same mappings are generated multiple times. +Only distinct mappings are created. +This means for example that if you specify `realmRolesPerUser=5` it can happen +that only 4 or less roles will be actually mapped. + + There is an option to use unique random sequences but is is disabled right now +because checking for uniqueness is CPU-intensive. + +- Mapping of client roles to a user right now is determined by a single parameter: `clientRolesPerUser`. + + Actually created mappings -- each of which contains specific client + a set of its roles -- is created +based on the list of randomly selected client roles of all clients in the realm. +This means the count of the actual client mappings isn't predictable. + + That would require specifying 2 parameters: `clientsPerUser` and `clientRolesPerClientPerUser` +which would say how many clients a user has roles assigned from, and the number of roles per each of these clients. + +- Number of resource servers depends on how the attribute `authorizationServicesEnabled` +is set for each client. This means the number isn't specified by any "perRealm" parameter. +If this is needed it can be implemented via a random mapping from a resource server entity +to a set of existing clients in a similar fashion to how a resource is selected for each resource permission. + +- The "resource type" attribute for each resource and resource-based permission defaults to +the default type of the parent resource server. +If it's needed a separate abstract/non-persistable entity ResourceType can be created in the model +to represent a set of resource types. The "resource type" attributes can then be set based on random mappings into this set. + +- Generating large number of users can take a long time with the default realm settings +which have the password hashing iterations set to a default value of 27500. +If you wish to speed this process up decrease the value of `hashIterations()` in attribute `realm.passwordPolicy`. + + Note that this will also significantly affect the performance results of the tests because +password hashing takes a major part of the server's compute resources. The results may +improve even by a factor of 10 or higher when the hashing is set to the minimum value of 1 itreration. +However it's on the expense of security. + diff --git a/testsuite/performance/README.md b/testsuite/performance/README.md index 0ccc4503ff..095313b4b5 100644 --- a/testsuite/performance/README.md +++ b/testsuite/performance/README.md @@ -26,8 +26,8 @@ mvn clean install # Make sure your Docker daemon is running THEN mvn verify -Pprovision -mvn verify -Pgenerate-data -Ddataset=100u2c -DnumOfWorkers=10 -DhashIterations=100 -mvn verify -Ptest -Ddataset=100u2c -DusersPerSec=2 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DmeasurementPeriod=60 -DfilterResults=true +mvn verify -Pgenerate-data -Ddataset=1r_10c_100u -DnumOfWorkers=10 +mvn verify -Ptest -Ddataset=1r_10c_100u -DusersPerSec=2 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DmeasurementPeriod=60 -DfilterResults=true ``` Now open the generated report in a browser - the link to .html file is displayed at the end of the test. @@ -39,7 +39,7 @@ mvn verify -Pteardown You can perform all phases in a single run: ``` -mvn verify -Pprovision,generate-data,test,teardown -Ddataset=100u2c -DnumOfWorkers=10 -DhashIterations=100 -DusersPerSec=4 -DrampUpPeriod=10 +mvn verify -Pprovision,generate-data,test,teardown -Ddataset=1r_10c_100u -DnumOfWorkers=10 -DusersPerSec=4 -DrampUpPeriod=10 ``` Note: The order in which maven profiles are listed does not determine the order in which profile related plugins are executed. `teardown` profile always executes last. @@ -103,6 +103,23 @@ it is necessary to update the generated Keycloak server configuration (inside `k adding a `clean` goal to the provisioning command like so: `mvn clean verify -Pprovision …`. It is *not* necessary to update this configuration when switching between `singlenode` and `cluster` deployments. +#### Manual Provisioning + +If you want to generate data or run the test against an already running instance of Keycloak server +you need to provide information about the system in a properties file. + +Create file: `tests/target/provisioned-system.properties` with the following properties: +``` +keycloak.frontend.servers=http://localhost:8080/auth +keycloak.admin.user=admin +keycloak.admin.password=admin +``` +and replace the values with your actual information. Then it will be possible to run tasks: `generate-data` and `test`. + +The tasks: `export-dump`, `import-dump` and `collect` (see below) are only available with the automated provisioning +because they require direct access to the provisioned services. + + ### Collect Artifacts Usage: `mvn verify -Pcollect` @@ -122,45 +139,31 @@ because it contains the `provisioned-system.properties` with information about t ### Generate Test Data -Usage: `mvn verify -P generate-data [-Ddataset=NAMED_PROPERTY_SET] [-DnumOfWorkers=N]`. The default dataset is `2u2c`. Workers default to `1`. +Usage: `mvn verify -P generate-data [-Ddataset=NAMED_PROPERTY_SET] [-DnumOfWorkers=N]`. Workers default to `1`. -The parameters are loaded from `tests/parameters/datasets/${dataset}.properties` file. -Individual properties can be overriden from command line via `-D` params. +The parameters are loaded from `tests/src/test/resources/dataset/${dataset}.properties` file with `${dataset}` defaulting to `default`. To use a custom properties file specify `-Ddataset.properties.file=ABSOLUTE_PATH_TO_FILE` instead of `-Ddataset`. To generate data using a different version of Keycloak Admin Client set property `-Dserver.version=SERVER_VERSION` to match the version of the provisioned server. -#### Dataset Parameters - -| Property | Description | Value in the Default Dataset | -| --- | --- | --- | -| `numOfRealms` | Number of realms to be created. | `1` | -| `usersPerRealm` | Number of users per realm. | `2` | -| `clientsPerRealm` | Number of clients per realm. | `2` | -| `realmRoles` | Number of realm-roles per realm. | `2` | -| `realmRolesPerUser` | Number of realm-roles assigned to a created user. Has to be less than or equal to `realmRoles`. | `2` | -| `clientRolesPerUser` | Number of client-roles assigned to a created user. Has to be less than or equal to `clientsPerRealm * clientRolesPerClient`. | `2` | -| `clientRolesPerClient` | Number of client-roles per created client. | `2` | -| `hashIterations` | Number of password hashing iterations. | `27500` | - +To delete the generated dataset add `-Ddelete=true` to the above command. Dataset is deleted by deleting individual realms. #### Examples: - Generate the default dataset. `mvn verify -P generate-data` -- Generate the `100u2c` dataset. `mvn verify -P generate-data -Ddataset=100u2c` -- Generate the `100u2c` dataset but override some parameters. `mvn verify -P generate-data -Ddataset=100u2c -DclientRolesPerUser=5 -DclientRolesPerClient=5` +- Generate the `1r_10c_100u` dataset. `mvn verify -P generate-data -Ddataset=1r_10c_100u` #### Export Database To export the generated data to a data-dump file enable profile `-P export-dump`. This will create a `${DATASET}.sql.gz` file next to the dataset properties file. -Example: `mvn verify -P generate-data,export-dump -Ddataset=100u2c` +Example: `mvn verify -P generate-data,export-dump -Ddataset=1r_10c_100u` #### Import Database To import data from an existing data-dump file use profile `-P import-dump`. -Example: `mvn verify -P import-dump -Ddataset=100u2c` +Example: `mvn verify -P import-dump -Ddataset=1r_10c_100u` If the dump file doesn't exist locally the script will attempt to download it from `${db.dump.download.site}` which defaults to `https://downloads.jboss.org/keycloak-qe/${server.version}` with `server.version` defaulting to `${project.version}` from `pom.xml`. @@ -221,11 +224,11 @@ When running the tests it is necessary to define the dataset to be used. - Run test specific test and dataset parameters: -`mvn verify -P test -Dtest.properties=oidc-login-logout -Ddataset=100u2c` +`mvn verify -P test -Dtest.properties=oidc-login-logout -Ddataset=1r_10c_100u` - 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` +`mvn verify -P test -Dtest.properties=admin-console -Ddataset=1r_10c_100u -DrampUpPeriod=30 -DwarmUpPeriod=60 -DusersPerSec=0.3` #### Running `OIDCRegisterAndLogoutSimulation` @@ -240,7 +243,7 @@ Running the user registration simulation requires a different approach to datase `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. +1. Generate or import dataset with 100 users: `mvn verify -P generate-data -Ddataset=1r_10c_100u`. 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=` diff --git a/testsuite/performance/tests/parameters/datasets/100r/100u2c.properties b/testsuite/performance/tests/parameters/datasets/100r/100u2c.properties deleted file mode 100644 index b4a370a704..0000000000 --- a/testsuite/performance/tests/parameters/datasets/100r/100u2c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=100 -usersPerRealm=100 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/100r/2u2c.properties b/testsuite/performance/tests/parameters/datasets/100r/2u2c.properties deleted file mode 100644 index 10e1cecd5e..0000000000 --- a/testsuite/performance/tests/parameters/datasets/100r/2u2c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=100 -usersPerRealm=2 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/100u2c.properties b/testsuite/performance/tests/parameters/datasets/100u2c.properties deleted file mode 100644 index 1b793770b5..0000000000 --- a/testsuite/performance/tests/parameters/datasets/100u2c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=1 -usersPerRealm=100 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/10r100u1c.properties b/testsuite/performance/tests/parameters/datasets/10r100u1c.properties deleted file mode 100644 index 68a3ae245a..0000000000 --- a/testsuite/performance/tests/parameters/datasets/10r100u1c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=10 -usersPerRealm=100 -clientsPerRealm=1 -realmRoles=100 -realmRolesPerUser=50 -clientRolesPerUser=0 -clientRolesPerClient=0 -hashIterations=27500 \ No newline at end of file diff --git a/testsuite/performance/tests/parameters/datasets/200ku200c.properties b/testsuite/performance/tests/parameters/datasets/200ku200c.properties deleted file mode 100644 index 9228099bcd..0000000000 --- a/testsuite/performance/tests/parameters/datasets/200ku200c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=1 -usersPerRealm=200000 -clientsPerRealm=200 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/200ku2kc.properties b/testsuite/performance/tests/parameters/datasets/200ku2kc.properties deleted file mode 100644 index 18c9a668f5..0000000000 --- a/testsuite/performance/tests/parameters/datasets/200ku2kc.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=1 -usersPerRealm=200000 -clientsPerRealm=2000 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/200r100u1c.properties b/testsuite/performance/tests/parameters/datasets/200r100u1c.properties deleted file mode 100644 index 4b8cf98337..0000000000 --- a/testsuite/performance/tests/parameters/datasets/200r100u1c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=200 -usersPerRealm=100 -clientsPerRealm=1 -realmRoles=100 -realmRolesPerUser=50 -clientRolesPerUser=0 -clientRolesPerClient=0 -hashIterations=27500 \ No newline at end of file diff --git a/testsuite/performance/tests/parameters/datasets/20r100u1c.properties b/testsuite/performance/tests/parameters/datasets/20r100u1c.properties deleted file mode 100644 index 4296e870ab..0000000000 --- a/testsuite/performance/tests/parameters/datasets/20r100u1c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=20 -usersPerRealm=100 -clientsPerRealm=1 -realmRoles=100 -realmRolesPerUser=50 -clientRolesPerUser=0 -clientRolesPerClient=0 -hashIterations=27500 \ No newline at end of file diff --git a/testsuite/performance/tests/parameters/datasets/2ku200c.properties b/testsuite/performance/tests/parameters/datasets/2ku200c.properties deleted file mode 100644 index 7b1591106b..0000000000 --- a/testsuite/performance/tests/parameters/datasets/2ku200c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=1 -usersPerRealm=2000 -clientsPerRealm=200 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/2u2c.properties b/testsuite/performance/tests/parameters/datasets/2u2c.properties deleted file mode 100644 index dd7c3301f7..0000000000 --- a/testsuite/performance/tests/parameters/datasets/2u2c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=1 -usersPerRealm=2 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/500ku500c.properties b/testsuite/performance/tests/parameters/datasets/500ku500c.properties deleted file mode 100644 index 88dc49cd40..0000000000 --- a/testsuite/performance/tests/parameters/datasets/500ku500c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=1 -usersPerRealm=500000 -clientsPerRealm=500 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/500r100u1c.properties b/testsuite/performance/tests/parameters/datasets/500r100u1c.properties deleted file mode 100644 index 2f4c1643ea..0000000000 --- a/testsuite/performance/tests/parameters/datasets/500r100u1c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=500 -usersPerRealm=100 -clientsPerRealm=1 -realmRoles=100 -realmRolesPerUser=50 -clientRolesPerUser=0 -clientRolesPerClient=0 -hashIterations=27500 \ No newline at end of file diff --git a/testsuite/performance/tests/parameters/datasets/50r100u1c.properties b/testsuite/performance/tests/parameters/datasets/50r100u1c.properties deleted file mode 100644 index b5c3475760..0000000000 --- a/testsuite/performance/tests/parameters/datasets/50r100u1c.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=50 -usersPerRealm=100 -clientsPerRealm=1 -realmRoles=100 -realmRolesPerUser=50 -clientRolesPerUser=0 -clientRolesPerClient=0 -hashIterations=27500 \ No newline at end of file diff --git a/testsuite/performance/tests/parameters/datasets/volume-testing/big.properties b/testsuite/performance/tests/parameters/datasets/volume-testing/big.properties deleted file mode 100644 index 4919199241..0000000000 --- a/testsuite/performance/tests/parameters/datasets/volume-testing/big.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=200 -usersPerRealm=1000000 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/volume-testing/medium.properties b/testsuite/performance/tests/parameters/datasets/volume-testing/medium.properties deleted file mode 100644 index b80e841a9b..0000000000 --- a/testsuite/performance/tests/parameters/datasets/volume-testing/medium.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=20 -usersPerRealm=10000 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/parameters/datasets/volume-testing/small.properties b/testsuite/performance/tests/parameters/datasets/volume-testing/small.properties deleted file mode 100644 index a281f9056c..0000000000 --- a/testsuite/performance/tests/parameters/datasets/volume-testing/small.properties +++ /dev/null @@ -1,8 +0,0 @@ -numOfRealms=2 -usersPerRealm=1000 -clientsPerRealm=2 -realmRoles=2 -realmRolesPerUser=2 -clientRolesPerUser=2 -clientRolesPerClient=2 -hashIterations=27500 diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml index 8ba8af0fd2..70281c89e5 100644 --- a/testsuite/performance/tests/pom.xml +++ b/testsuite/performance/tests/pom.xml @@ -34,11 +34,11 @@ singlenode ${provisioner}/4cpus/${deployment} - 2u2c + default oidc-login-logout ${project.basedir}/parameters/provisioning/${provisioning.properties}.properties - ${project.basedir}/parameters/datasets/${dataset}.properties + ${project.basedir}/src/test/resources/dataset/${dataset}.properties ${project.basedir}/parameters/test/${test.properties}.properties ${project.build.directory}/provisioned-system.properties @@ -56,9 +56,14 @@ 2.2.1 3.2.2 3.3.0.Final + + 2.9.6 + ${jackson.version} + ${jackson.databind.version} keycloak.OIDCLoginAndLogoutSimulation true + true @@ -93,11 +98,35 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.annotations.version} + org.keycloak keycloak-admin-client ${server.version} + + commons-configuration + commons-configuration + 1.10 + + + commons-validator + commons-validator + 1.6 + + + org.freemarker + freemarker + + + junit + junit + test + org.jboss.spec.javax.ws.rs jboss-jaxrs-api_2.0_spec @@ -137,8 +166,12 @@ - src/test/resources true + src/test/resources + + **/*.gz + **/*.properties + @@ -188,7 +221,6 @@ true ${provisioning.properties.file} - ${dataset.properties.file} ${test.properties.file} @@ -261,6 +293,7 @@ ${gatling.skip.run} true true + true -Dproject.build.directory=${project.build.directory} @@ -268,14 +301,17 @@ -DauthUser=${keycloak.admin.user} -DauthPassword=${keycloak.admin.password} - -DnumOfRealms=${numOfRealms} + + -Ddataset.properties.file=${dataset.properties.file} + + -DusersPerSec=${usersPerSec} -DrampUpPeriod=${rampUpPeriod} @@ -309,6 +345,17 @@ ${project.basedir} + + + maven-surefire-plugin + + + ${dataset.properties.file} + + ${surefire.skip.run} + + + @@ -458,10 +505,14 @@ generate-data - 0 - false - false - false + false + 5 + 60 + 60 + 10000 + 10000 + 100000 + 2g @@ -469,30 +520,6 @@ org.codehaus.mojo exec-maven-plugin - - generate-data - pre-integration-test - - exec - - - java - ${project.build.directory} - - -classpath - - -DnumOfRealms=${numOfRealms} - -DusersPerRealm=${usersPerRealm} - -DclientsPerRealm=${clientsPerRealm} - -DrealmRoles=${realmRoles} - -DrealmRolesPerUser=${realmRolesPerUser} - -DclientRolesPerUser=${clientRolesPerUser} - -DclientRolesPerClient=${clientRolesPerClient} - -DhashIterations=${hashIterations} - org.keycloak.performance.RealmsConfigurationBuilder - - - load-data pre-integration-test @@ -503,20 +530,33 @@ java ${project.build.directory} + -classpath - ${trustStoreArg} - ${trustStorePasswordArg} + + -Xms64m + -Xmx${max.heap} + -XX:MetaspaceSize=96M + -XX:MaxMetaspaceSize=256m + -Dkeycloak.server.uris=${keycloak.frontend.servers} -DauthUser=${keycloak.admin.user} -DauthPassword=${keycloak.admin.password} -DnumOfWorkers=${numOfWorkers} - -DstartAtRealmIdx=${startAtRealmIdx} - -DignoreConflicts=${ignoreConflicts} - -DskipRealmRoles=${skipRealmRoles} - -DskipClientRoles=${skipClientRoles} - org.keycloak.performance.RealmsConfigurationLoader - benchmark-realms.json + -Ddataset.properties.file=${dataset.properties.file} + ${trustStoreArg} + ${trustStorePasswordArg} + + -Ddelete=${delete} + -Dlog.every=${log.every} + -Dqueue.timeout=${queue.timeout} + -Dshutdown.timeout=${shutdown.timeout} + -Dtemplate.cache.size=${template.cache.size} + -Drandoms.cache.size=${randoms.cache.size} + -Dentity.cache.size=${entity.cache.size} + + org.keycloak.performance.dataset.DatasetLoader + @@ -594,6 +634,13 @@ + + junit + + false + + + collect diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogLine.java b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogLine.java similarity index 99% rename from testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogLine.java rename to testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogLine.java index 333b2be71a..b132391101 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogLine.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogLine.java @@ -1,4 +1,4 @@ -package org.keycloak.performance.log; +package org.keycloak.gatling.log; /** * @author Marko Strukelj diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogProcessor.java b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogProcessor.java similarity index 99% rename from testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogProcessor.java rename to testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogProcessor.java index 1eb299cfc9..31d59b9fc6 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogProcessor.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogProcessor.java @@ -1,4 +1,4 @@ -package org.keycloak.performance.log; +package org.keycloak.gatling.log; import java.io.File; import java.io.FileOutputStream; diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogReader.java b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogReader.java similarity index 95% rename from testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogReader.java rename to testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogReader.java index 7cfdbbfdd7..e560a436c1 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/LogReader.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/LogReader.java @@ -1,4 +1,4 @@ -package org.keycloak.performance.log; +package org.keycloak.gatling.log; import java.io.BufferedReader; import java.io.File; diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/Stats.java b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/Stats.java similarity index 98% rename from testsuite/performance/tests/src/main/java/org/keycloak/performance/log/Stats.java rename to testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/Stats.java index aa5a831e3f..698f30d5cd 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/log/Stats.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/gatling/log/Stats.java @@ -1,4 +1,4 @@ -package org.keycloak.performance.log; +package org.keycloak.gatling.log; import java.util.ArrayList; import java.util.HashMap; 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 94824d71bb..174e3beb31 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 @@ -1,8 +1,8 @@ package org.keycloak.performance; import java.text.SimpleDateFormat; -import org.keycloak.performance.util.FilteredIterator; -import org.keycloak.performance.util.LoopingIterator; +import org.keycloak.performance.iteration.FilteredIterator; +import org.keycloak.performance.iteration.LoopingIterator; import java.util.Arrays; import java.util.Iterator; @@ -10,6 +10,8 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ThreadLocalRandom; +import org.apache.commons.configuration.CombinedConfiguration; +import org.jboss.logging.Logger; import static org.keycloak.performance.RealmsConfigurationBuilder.computeAppUrl; import static org.keycloak.performance.RealmsConfigurationBuilder.computeClientId; @@ -19,14 +21,24 @@ import static org.keycloak.performance.RealmsConfigurationBuilder.computeLastNam import static org.keycloak.performance.RealmsConfigurationBuilder.computePassword; import static org.keycloak.performance.RealmsConfigurationBuilder.computeSecret; import static org.keycloak.performance.RealmsConfigurationBuilder.computeUsername; +import org.keycloak.performance.util.CombinedConfigurationNoInterpolation; /** * @author Marko Strukelj + * @author Tomas Kyjovsky */ public class TestConfig { + private static final Logger LOGGER = Logger.getLogger(TestConfig.class); + + public static final CombinedConfiguration CONFIG; + + static { + CONFIG = new CombinedConfigurationNoInterpolation(); + } + // - // Settings used by RealmsConfigurationBuilder only - when generating the dataset + // Settings used by RealmsConfigurationBuilder only - when generating the DATASET // public static final int hashIterations = Integer.getInteger("hashIterations", 27500); @@ -49,7 +61,7 @@ public class TestConfig { public static final String authClient = System.getProperty("authClient", "admin-cli"); // - // Settings used by RealmsConfigurationBuilder to generate the dataset and by tests to work within constraints of the dataset + // Settings used by RealmsConfigurationBuilder to generate the DATASET and by tests to work within constraints of the DATASET // public static final int numOfRealms = Integer.getInteger("numOfRealms", 1); public static final int usersPerRealm = Integer.getInteger("usersPerRealm", 2); @@ -59,12 +71,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 + // 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 // @@ -92,7 +104,7 @@ public class TestConfig { public static final String serverUris; public static final List serverUrisList; - // Round-robin infinite iterator that directs each next session to the next server + // Round-robin infinite ENTITY_ITERATOR that directs each next session to the next server public static final Iterator serverUrisIterator; static { @@ -109,7 +121,7 @@ public class TestConfig { serverUrisList = Arrays.asList(serverUris.split(" ")); serverUrisIterator = new LoopingIterator<>(serverUrisList); } - + // assertion properties public static final int maxFailedRequests = Integer.getInteger("maxFailedRequests", 0); public static final int maxMeanReponseTime = Integer.getInteger("maxMeanReponseTime", 300); @@ -139,33 +151,33 @@ public class TestConfig { public static String toStringCommonTestParameters() { return String.format( - " usersPerSec: %s\n" + - " rampUpPeriod: %s\n"+ - " warmUpPeriod: %s\n"+ - " measurementPeriod: %s\n"+ - " filterResults: %s\n"+ - " userThinkTime: %s\n"+ - " refreshTokenPeriod: %s\n"+ - " logoutPct: %s", - usersPerSec, rampUpPeriod, warmUpPeriod, measurementPeriod, filterResults, userThinkTime, refreshTokenPeriod, logoutPct); + " usersPerSec: %s\n" + + " rampUpPeriod: %s\n" + + " warmUpPeriod: %s\n" + + " measurementPeriod: %s\n" + + " filterResults: %s\n" + + " userThinkTime: %s\n" + + " refreshTokenPeriod: %s\n" + + " logoutPct: %s", + usersPerSec, rampUpPeriod, warmUpPeriod, measurementPeriod, filterResults, userThinkTime, refreshTokenPeriod, logoutPct); } - + public static SimpleDateFormat SIMPLE_TIME = new SimpleDateFormat("HH:mm:ss"); - + public static String toStringTimestamps() { return String.format(" simulationStartTime: %s\n" + " warmUpStartTime: %s\n" + " measurementStartTime: %s\n" + " measurementEndTime: %s", - SIMPLE_TIME.format(simulationStartTime), - SIMPLE_TIME.format(warmUpStartTime), - SIMPLE_TIME.format(measurementStartTime), + SIMPLE_TIME.format(simulationStartTime), + SIMPLE_TIME.format(warmUpStartTime), + SIMPLE_TIME.format(measurementStartTime), SIMPLE_TIME.format(measurementEndTime)); } public static String toStringDatasetProperties() { return String.format( - " numOfRealms: %s%s\n" + " numOfRealms: %s%s\n" + " usersPerRealm: %s%s\n" + " clientsPerRealm: %s\n" + " realmRoles: %s\n" @@ -173,23 +185,23 @@ public class TestConfig { + " 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, + numOfRealms, sequentialRealms ? ", sequential iteration starting from " + sequentialRealmsFrom : "", + usersPerRealm, sequentialUsers ? ", sequential iteration starting from " + sequentialUsersFrom : "", + clientsPerRealm, + realmRoles, + realmRolesPerUser, + clientRolesPerUser, + clientRolesPerClient, hashIterations); } - + public static String toStringAssertionProperties() { return String.format(" maxFailedRequests: %s\n" + " maxMeanReponseTime: %s", maxFailedRequests, maxMeanReponseTime); } - + public static Iterator sequentialUsersIterator(final String realm) { return new Iterator() { @@ -208,9 +220,9 @@ public class TestConfig { } String user = computeUsername(realm, idx); - String firstName= computeFirstName(idx); + String firstName = computeFirstName(idx); idx += 1; - return new UserInfo(user, + return new UserInfo(user, computePassword(user), firstName, computeLastName(realm), @@ -233,7 +245,7 @@ public class TestConfig { public UserInfo next() { int idx = ThreadLocalRandom.current().nextInt(usersPerRealm); String user = computeUsername(realm, idx); - return new UserInfo(user, + return new UserInfo(user, computePassword(user), computeFirstName(idx), computeLastName(realm), @@ -267,7 +279,7 @@ public class TestConfig { return new Iterator() { int idx = sequentialRealms ? sequentialRealmsFrom : 0; - + @Override public boolean hasNext() { return true; @@ -318,5 +330,5 @@ public class TestConfig { throw new RuntimeException("The `logoutPct` needs to be between 0 and 100."); } } - + } diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Creatable.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Creatable.java new file mode 100644 index 0000000000..6e5bc961cf --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Creatable.java @@ -0,0 +1,74 @@ +package org.keycloak.performance.dataset; + +import java.io.IOException; +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.core.Response; +import static org.keycloak.admin.client.CreatedResponseUtil.getCreatedId; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.performance.templates.EntityTemplate; + +/** + * + * @author tkyjovsk + */ +public interface Creatable extends Updatable { + + public static final String HTTP_409_SUFFIX = "409 Conflict"; + + public REP read(Keycloak adminClient); + + public default String getIdAndReadIfNull(Keycloak adminClient) { + if (getId() == null) { + logger().debug("id of entity " + this + " was null, reading from server"); + readAndSetId(adminClient); + } + return getId(); + } + + public default void readAndSetId(Keycloak adminClient) { + setId(getIdFromRepresentation(read(adminClient))); + } + + public Response create(Keycloak adminClient); + + public default boolean createCheckingForConflict(Keycloak adminClient) { + logger().trace("creating " + this); + boolean conflict = false; + try { + Response response = create(adminClient); + if (response == null) { + readAndSetId(adminClient); + } else { + String responseBody = response.readEntity(String.class); + response.close(); + if (response.getStatus() == 409) { // some endpoints dont't throw exception on 409, throwing here + throw new ClientErrorException(HTTP_409_SUFFIX, response); + } + if (responseBody != null && !responseBody.isEmpty()) { + logger().trace(responseBody); + setRepresentation(EntityTemplate.OBJECT_MAPPER.readValue(responseBody, (Class) getRepresentation().getClass())); + } else { + setId(getCreatedId(response)); + } + } + } catch (ClientErrorException ex) { + if (ex.getResponse().getStatus() == 409) { + conflict = true; + logger().trace("entity already exists"); + readAndSetId(adminClient); + } else { + throw ex; + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return conflict; + } + + public default void createOrUpdateExisting(Keycloak adminClient) { + if (createCheckingForConflict(adminClient)) { + update(adminClient); + } + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Dataset.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Dataset.java new file mode 100644 index 0000000000..32224d792b --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Dataset.java @@ -0,0 +1,153 @@ +package org.keycloak.performance.dataset; + +import java.util.Iterator; +import org.keycloak.performance.dataset.idm.Realm; +import java.util.List; +import java.util.stream.Stream; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.ClientRole; +import org.keycloak.performance.dataset.idm.ClientRoleMappings; +import org.keycloak.performance.dataset.idm.Credential; +import org.keycloak.performance.dataset.idm.Group; +import org.keycloak.performance.dataset.idm.RealmRole; +import org.keycloak.performance.dataset.idm.RoleMappings; +import org.keycloak.performance.dataset.idm.User; +import org.keycloak.performance.dataset.idm.authorization.ClientPolicy; +import org.keycloak.performance.dataset.idm.authorization.JsPolicy; +import org.keycloak.performance.dataset.idm.authorization.Resource; +import org.keycloak.performance.dataset.idm.authorization.ResourcePermission; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.RolePolicy; +import org.keycloak.performance.dataset.idm.authorization.Scope; +import org.keycloak.performance.dataset.idm.authorization.ScopePermission; +import org.keycloak.performance.dataset.idm.authorization.UserPolicy; +import org.keycloak.performance.iteration.RandomIterator; + +/** + * + * @author tkyjovsk + */ +public class Dataset extends Entity { + + private List realms; + + private List allUsers; + private List allClients; + + @Override + public DatasetRepresentation newRepresentation() { + return new DatasetRepresentation(); + } + + @Override + public String toString() { + String s = getRepresentation().getName(); + return s == null || s.isEmpty() ? "dataset" : s; + } + + public List getRealms() { + return realms; + } + + public void setRealms(List realms) { + this.realms = realms; + } + + public List getAllUsers() { + return allUsers; + } + + public void setAllUsers(List allUsers) { + this.allUsers = allUsers; + } + + public Iterator randomUsersIterator() { + return new RandomIterator<>(getAllUsers()); + } + + public List getAllClients() { + return allClients; + } + + public void setAllClients(List allClients) { + this.allClients = allClients; + } + + public Iterator randomRealmIterator() { + return new RandomIterator<>(getRealms()); + } + + public Stream realms() { + return getRealms().stream(); + } + + public Stream realmRoles() { + return getRealms().stream().map(Realm::getRealmRoles).flatMap(List::stream); + } + + public Stream clients() { + return getRealms().stream().map(Realm::getClients).flatMap(List::stream); + } + + public Stream clientRoles() { + return clients().map(Client::getClientRoles).flatMap(List::stream); + } + + public Stream users() { + return getRealms().stream().map(Realm::getUsers).flatMap(List::stream); + } + + public Stream credentials() { + return users().map(User::getCredentials).flatMap(List::stream); + } + + public Stream> userRealmRoleMappings() { + return users().map(User::getRealmRoleMappings); + } + + public Stream> userClientRoleMappings() { + return users().map(User::getClientRoleMappingsList).flatMap(List::stream); + } + + public Stream groups() { + return getRealms().stream().map(Realm::getGroups).flatMap(List::stream); + } + + public Stream resourceServers() { + return clients().filter(c -> c.getRepresentation().getAuthorizationServicesEnabled()) + .map(c -> c.getResourceServer()); + } + + public Stream scopes() { + return resourceServers().map(rs -> rs.getScopes()).flatMap(List::stream); + } + + public Stream resources() { + return resourceServers().map(rs -> rs.getResources()).flatMap(List::stream); + } + + public Stream rolePolicies() { + return resourceServers().map(rs -> rs.getRolePolicies()).flatMap(List::stream); + } + + public Stream jsPolicies() { + return resourceServers().map(rs -> rs.getJsPolicies()).flatMap(List::stream); + } + + public Stream userPolicies() { + return resourceServers().map(rs -> rs.getUserPolicies()).flatMap(List::stream); + } + + public Stream clientPolicies() { + return resourceServers().map(rs -> rs.getClientPolicies()).flatMap(List::stream); + } + + public Stream resourcePermissions() { + return resourceServers().map(rs -> rs.getResourcePermissions()).flatMap(List::stream); + } + + public Stream scopePermissions() { + return resourceServers().map(rs -> rs.getScopePermissions()).flatMap(List::stream); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetLoader.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetLoader.java new file mode 100644 index 0000000000..4eaafd795e --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetLoader.java @@ -0,0 +1,205 @@ +package org.keycloak.performance.dataset; + +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import static java.util.concurrent.TimeUnit.SECONDS; +import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; +import org.apache.commons.lang.Validate; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.performance.TestConfig; +import org.keycloak.performance.templates.DatasetTemplate; +import org.keycloak.performance.util.Loggable; + +/** + * + * @author tkyjovsk + */ +public class DatasetLoader implements Loggable { + + private static final boolean DELETE = Boolean.parseBoolean(System.getProperty("delete", "false")); + private static final int LOG_EVERY = Integer.parseInt(System.getProperty("log.every", "5")); + private static final int QUEUE_TIMEOUT = Integer.parseInt(System.getProperty("queue.timeout", "60")); + private static final int THREADPOOL_SHUTDOWN_TIMEOUT = Integer.parseInt(System.getProperty("shutdown.timeout", "60")); + + public static void main(String[] args) { + DatasetTemplate template = new DatasetTemplate(); + template.validateConfiguration(); + DatasetLoader loader = new DatasetLoader(template.produce(), DELETE); + loader.processDataset(); + } + + private final Dataset dataset; + private final boolean delete; + + private final BlockingQueue adminClients = new LinkedBlockingQueue<>(); + private Throwable error = null; + + Map counter = new LinkedHashMap<>(); + long startTime; + long nextLoggingTime; + + public DatasetLoader(Dataset dataset, boolean delete) { + Validate.notNull(dataset); + this.dataset = dataset; + this.delete = delete; + for (int i = 0; i < TestConfig.numOfWorkers; i++) { + adminClients.add(Keycloak.getInstance( + TestConfig.serverUrisIterator.next(), + TestConfig.authRealm, + TestConfig.authUser, + TestConfig.authPassword, + TestConfig.authClient)); + } + } + + private void processDataset() { + if (delete) { + logger().info("Deleting dataset."); + processEntities(dataset.realms()); + logProcessedEntityCounts(true); + closeAdminClients(); + logger().info("Dataset deleted."); + } else { + logger().info("Creating dataset."); + processEntities(dataset.realms()); + processEntities(dataset.realmRoles()); + processEntities(dataset.clients()); + processEntities(dataset.clientRoles()); + processEntities(dataset.users()); + processEntities(dataset.credentials()); + processEntities(dataset.userRealmRoleMappings()); + processEntities(dataset.userClientRoleMappings()); + processEntities(dataset.groups()); + processEntities(dataset.resourceServers()); + processEntities(dataset.scopes()); + processEntities(dataset.resources()); + processEntities(dataset.rolePolicies()); + processEntities(dataset.jsPolicies()); + processEntities(dataset.userPolicies()); + processEntities(dataset.clientPolicies()); + processEntities(dataset.resourcePermissions()); + processEntities(dataset.scopePermissions()); + logProcessedEntityCounts(true); + closeAdminClients(); + logger().info("Dataset created."); + } + } + + private void processEntities(Stream stream) { + if (!errorReported()) { + Iterator iterator = stream.iterator(); + ExecutorService threadPool = Executors.newFixedThreadPool(TestConfig.numOfWorkers); + BlockingQueue queue = new LinkedBlockingQueue<>(TestConfig.numOfWorkers + 5); + try { + while (iterator.hasNext() && !errorReported()) { + logProcessedEntityCounts(false); + try { + if (queue.offer(iterator.next(), QUEUE_TIMEOUT, SECONDS)) { + threadPool.execute(() -> { + if (!errorReported()) { + try { + + Updatable updatable = queue.take(); + Keycloak adminClient = adminClients.take(); + + try { + + if (delete) { + updatable.deleteOrIgnoreMissing(adminClient); + } else { + if (updatable instanceof Creatable) { + ((Creatable) updatable).createOrUpdateExisting(adminClient); + } else { + updatable.update(adminClient); + } + } + + confirmEntityAsProcessed(updatable); + + } finally { + adminClients.add(adminClient); // return client for reuse + } + + } catch (Exception ex) { + reportError(ex); + } + } + }); + } else { + reportError(new TimeoutException("Waiting for executor timed out.")); + } + } catch (InterruptedException ex) { + reportError(ex); + } + } + } catch (Exception ex) { + reportError(ex); + } + // shut down threadpool + if (errorReported()) { + logger().error("Exception thrown from executor service. Shutting down."); + threadPool.shutdownNow(); + throw new RuntimeException(error); + } else { + try { + threadPool.shutdown(); + threadPool.awaitTermination(THREADPOOL_SHUTDOWN_TIMEOUT, SECONDS); + if (!threadPool.isTerminated()) { + throw new IllegalStateException("Executor service still not terminated."); + } + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + } + } + + private synchronized void confirmEntityAsProcessed(Updatable entity) { + String key = entity.getClass().getSimpleName(); + Integer count = counter.get(key); + count = count == null ? 1 : ++count; + counter.put(key, count); + } + + private synchronized void logProcessedEntityCounts(boolean ignoreTimestamp) { + long time = new Date().getTime(); + if (startTime == 0) { + startTime = new Date().getTime(); + } + if (!counter.isEmpty() && (ignoreTimestamp || time > nextLoggingTime)) { + + StringBuilder sb = new StringBuilder(); + counter.entrySet().forEach(e -> sb.append(String.format("\n%-20s %s", e.getKey(), e.getValue()))); + logger().info(String.format("Time: +%s s\n%s entities: %s\n", + (time - startTime) / 1000, + (delete ? "Deleted" : "Created"), + sb.toString() + )); + nextLoggingTime = time + LOG_EVERY * 1000; + } + } + + private synchronized boolean errorReported() { + return error != null; + } + + private synchronized void reportError(Throwable ex) { + logProcessedEntityCounts(true); + logger().error("Error occured: " + ex); + this.error = ex; + } + + public void closeAdminClients() { + while (!adminClients.isEmpty()) { + adminClients.poll().close(); + } + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetRepresentation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetRepresentation.java new file mode 100644 index 0000000000..3fbe671a35 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetRepresentation.java @@ -0,0 +1,19 @@ +package org.keycloak.performance.dataset; + +/** + * + * @author tkyjovsk + */ +public class DatasetRepresentation { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Entity.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Entity.java new file mode 100644 index 0000000000..988fd9732c --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Entity.java @@ -0,0 +1,45 @@ +package org.keycloak.performance.dataset; + +import org.apache.commons.lang.Validate; + +/** + * + * @author tkyjovsk + * @param representation type + */ +public abstract class Entity implements Representable { + + private REP representation; + + public Entity() { + setRepresentation(newRepresentation()); + } + + @Override + public REP getRepresentation() { + return representation; + } + + @Override + public final void setRepresentation(REP representation) { + Validate.notNull(representation); + this.representation = representation; + } + + public String simpleClassName() { + return this.getClass().getSimpleName(); + } + + @Override + public int hashCode() { + return simpleClassName().hashCode(); + } + + @Override + public boolean equals(Object other) { + return this == other || (other != null + && this.getClass() == other.getClass() + && this.hashCode() == ((Entity) other).hashCode()); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/NestedEntity.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/NestedEntity.java new file mode 100644 index 0000000000..a8c3a5ad20 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/NestedEntity.java @@ -0,0 +1,66 @@ +package org.keycloak.performance.dataset; + +import org.apache.commons.lang.Validate; +import static org.keycloak.performance.iteration.RandomBooleans.getRandomBooleans; +import static org.keycloak.performance.iteration.RandomIntegers.getRandomIntegers; +import org.keycloak.performance.util.ValidateNumber; + +/** + * + * @author tkyjovsk + * @param parent entity + */ +public abstract class NestedEntity extends Entity { + + private final PE parentEntity; + + private final int index; + private final int seed; + + public NestedEntity(PE parentEntity, int index) { + Validate.notNull(parentEntity); + this.parentEntity = parentEntity; + ValidateNumber.minValue(index, 0); + this.index = index; + this.seed = parentEntity.hashCode() + simpleClassName().hashCode(); + } + + public NestedEntity(PE parentEntity) { + this(parentEntity, 0); + } + + public PE getParentEntity() { + return parentEntity; + } + + public synchronized final int getIndex() { + return index; + } + + public synchronized int getSeed() { + return seed; + } + + @Override + public synchronized int hashCode() { + return simpleClassName().hashCode() * getIndex() + getParentEntity().hashCode(); + } + + @Override + public boolean equals(Object other) { + return super.equals(other); + } + + public synchronized int indexBasedRandomInt(int bound) { + return getRandomIntegers(getSeed(), bound).get(getIndex()); + } + + public synchronized boolean indexBasedRandomBool(int truePercentage) { + return getRandomBooleans(getSeed(), truePercentage).get(getIndex()); + } + + public synchronized boolean indexBasedRandomBool() { + return indexBasedRandomBool(50); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Representable.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Representable.java new file mode 100644 index 0000000000..e18ecf66c2 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Representable.java @@ -0,0 +1,57 @@ +package org.keycloak.performance.dataset; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.apache.commons.lang.Validate; +import org.keycloak.performance.util.Loggable; +import static org.keycloak.util.JsonSerialization.writeValueAsString; + +/** + * + * @author tkyjovsk + * @param representation + */ +public interface Representable extends Loggable { + + public REP newRepresentation(); + + public REP getRepresentation(); + + public void setRepresentation(REP representation); + + public default void setId(String uuid) { + if (uuid == null) { + logger().debug(this.getClass().getSimpleName() + " " + this + " " + " setId " + uuid); + throw new IllegalArgumentException(); + } + try { + Class c = (Class) getRepresentation().getClass(); + Method setId = c.getMethod("setId", String.class); + setId.invoke(getRepresentation(), uuid); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + + public default String getIdFromRepresentation(REP representation) { + Validate.notNull(representation); + try { + Class c = (Class) representation.getClass(); + Method getId = c.getMethod("getId"); + Validate.notNull(getId); + return (String) getId.invoke(representation); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + + public default String getId() { + return getIdFromRepresentation(getRepresentation()); + } + + public default String toJSON() throws IOException { + return writeValueAsString(getRepresentation()); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Updatable.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Updatable.java new file mode 100644 index 0000000000..c6d92f8723 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Updatable.java @@ -0,0 +1,25 @@ +package org.keycloak.performance.dataset; + +import javax.ws.rs.NotFoundException; +import org.keycloak.admin.client.Keycloak; + +/** + * For entities with no id. + * + * @author tkyjovsk + */ +public interface Updatable extends Representable { + + public void update(Keycloak adminClient); + + public void delete(Keycloak adminClient); + + public default void deleteOrIgnoreMissing(Keycloak adminClient) { + try { + delete(adminClient); + } catch (NotFoundException ex) { + logger().info(String.format("Entity %s not found. Considering as deleted.", this)); + } + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/Attribute.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/Attribute.java new file mode 100644 index 0000000000..c1d59d6342 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/Attribute.java @@ -0,0 +1,17 @@ +package org.keycloak.performance.dataset.attr; + +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.NestedEntity; + +/** + * + * @author tkyjovsk + * @param parent entity + * @param representation + */ +public abstract class Attribute extends NestedEntity { + + public Attribute(PE attributeOwner, int index) { + super(attributeOwner, index); + } +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeMap.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeMap.java new file mode 100644 index 0000000000..509d795867 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeMap.java @@ -0,0 +1,21 @@ +package org.keycloak.performance.dataset.attr; + +import java.util.HashMap; +import java.util.List; + +/** + * + * @author tkyjovsk + */ +public class AttributeMap extends HashMap { + + public AttributeMap(List>> attributes) { + attributes.forEach(attribute -> { + put( + attribute.getRepresentation().getName(), + attribute.getRepresentation().getValue() + ); + }); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeRepresentation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeRepresentation.java new file mode 100644 index 0000000000..df4c812dde --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeRepresentation.java @@ -0,0 +1,28 @@ +package org.keycloak.performance.dataset.attr; + +/** + * + * @author tkyjovsk + */ +public abstract class AttributeRepresentation { + + private String name; + private V value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttribute.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttribute.java new file mode 100644 index 0000000000..ae109c934a --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttribute.java @@ -0,0 +1,21 @@ +package org.keycloak.performance.dataset.attr; + +import org.keycloak.performance.dataset.Entity; + +/** + * + * @author tkyjovsk + * @param + */ +public class StringAttribute extends Attribute { + + public StringAttribute(PE attributeOwner, int index) { + super(attributeOwner, index); + } + + @Override + public StringAttributeRepresentation newRepresentation() { + return new StringAttributeRepresentation(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttributeRepresentation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttributeRepresentation.java new file mode 100644 index 0000000000..69ceea78f1 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttributeRepresentation.java @@ -0,0 +1,8 @@ +package org.keycloak.performance.dataset.attr; + +/** + * + * @author tkyjovsk + */ +public class StringAttributeRepresentation extends AttributeRepresentation { +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttribute.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttribute.java new file mode 100644 index 0000000000..20ba930ed8 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttribute.java @@ -0,0 +1,21 @@ +package org.keycloak.performance.dataset.attr; + +import org.keycloak.performance.dataset.Entity; + +/** + * + * @author tkyjovsk + * @param owner entity + */ +public class StringListAttribute extends Attribute { + + public StringListAttribute(PE attributeOwner, int index) { + super(attributeOwner, index); + } + + @Override + public StringListAttributeRepresentation newRepresentation() { + return new StringListAttributeRepresentation(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttributeRepresentation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttributeRepresentation.java new file mode 100644 index 0000000000..6effadfbbc --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttributeRepresentation.java @@ -0,0 +1,10 @@ +package org.keycloak.performance.dataset.attr; + +import java.util.List; + +/** + * + * @author tkyjovsk + */ +public class StringListAttributeRepresentation extends AttributeRepresentation> { +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Client.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Client.java new file mode 100644 index 0000000000..4c010e19ec --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Client.java @@ -0,0 +1,80 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + */ +public class Client extends NestedEntity + implements Creatable { + + private List clientRoles; + private ResourceServer resourceServer; + + public Client(Realm realm, int index) { + super(realm, index); + } + + @Override + public ClientRepresentation newRepresentation() { + return new ClientRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getClientId(); + } + + public Realm getRealm() { + return getParentEntity(); + } + + public List getClientRoles() { + return clientRoles; + } + + public void setClientRoles(List clientRoles) { + this.clientRoles = clientRoles; + } + + public ResourceServer getResourceServer() { + return resourceServer; + } + + public void setResourceServer(ResourceServer resourceServer) { + this.resourceServer = resourceServer; + } + + public synchronized ClientResource resource(Keycloak adminClient) { + return getRealm().resource(adminClient).clients().get(getIdAndReadIfNull(adminClient)); + } + + @Override + public synchronized ClientRepresentation read(Keycloak adminClient) { + return getRealm().resource(adminClient).clients().findByClientId(getRepresentation().getClientId()).get(0); + } + + @Override + public synchronized Response create(Keycloak adminClient) { + return getRealm().resource(adminClient).clients().create(getRepresentation()); + } + + @Override + public synchronized void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public synchronized void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRole.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRole.java new file mode 100644 index 0000000000..0476fe9d4c --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRole.java @@ -0,0 +1,37 @@ +package org.keycloak.performance.dataset.idm; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleByIdResource; +import org.keycloak.admin.client.resource.RolesResource; +import org.keycloak.representations.idm.RoleRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ClientRole extends Role { + + public ClientRole(Client client, int index) { + super(client, index); + } + + public Client getClient() { + return getParentEntity(); + } + + @Override + public RolesResource rolesResource(Keycloak adminClient) { + return getClient().resource(adminClient).roles(); + } + + @Override + public RoleByIdResource roleByIdResource(Keycloak adminClient) { + return getClient().getRealm().resource(adminClient).rolesById(); + } + + @Override + public RoleRepresentation newRepresentation() { + return new RoleRepresentation(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRoleMappings.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRoleMappings.java new file mode 100644 index 0000000000..c5cc278dcc --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRoleMappings.java @@ -0,0 +1,40 @@ +package org.keycloak.performance.dataset.idm; + +import org.keycloak.admin.client.Keycloak; + +/** + * + * @author tkyjovsk + */ +public class ClientRoleMappings extends RoleMappings { + + private final Client client; + + public ClientRoleMappings(RM roleMapper, Client client, RoleMappingsRepresentation representation) { + super(roleMapper, representation); + this.client = client; + } + + @Override + public String toString() { + return String.format("%s/role-mappings/%s", getRoleMapper(), getClient()); + } + + @Override + public RoleMapper getRoleMapper() { + return getParentEntity(); + } + + public Client getClient() { + return client; + } + + @Override + public void update(Keycloak adminClient) { + getRoleMapper() + .roleMappingResource(adminClient) + .clientLevel(getClient().getId()) + .add(getRepresentation()); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Credential.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Credential.java new file mode 100644 index 0000000000..28026c3aab --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Credential.java @@ -0,0 +1,53 @@ +package org.keycloak.performance.dataset.idm; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.representations.idm.CredentialRepresentation; +import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD; +import org.keycloak.performance.dataset.Updatable; + +/** + * + * @author tkyjovsk + */ +public class Credential extends NestedEntity + implements Updatable { + + public Credential(User user, int index) { + super(user, index); + } + + @Override + public CredentialRepresentation newRepresentation() { + return new CredentialRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getType(); + } + + public User getUser() { + return getParentEntity(); + } + + @Override + public void update(Keycloak adminClient) { + if (getRepresentation().getType().equals(PASSWORD)) { + resource(adminClient).resetPassword(getRepresentation()); + } else { + logger().warn("Cannot reset password. Non-password credetial type."); + } + } + + public UserResource resource(Keycloak adminClient) { + return getUser().resource(adminClient); + } + + @Override + public void delete(Keycloak adminClient) { + throw new UnsupportedOperationException(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Group.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Group.java new file mode 100644 index 0000000000..ab8c3d075d --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Group.java @@ -0,0 +1,59 @@ +package org.keycloak.performance.dataset.idm; + +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.GroupResource; +import org.keycloak.admin.client.resource.RoleMappingResource; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + */ +public class Group extends RoleMapper implements Creatable { + + public Group(Realm realm, int index) { + super(realm, index); + } + + @Override + public GroupRepresentation newRepresentation() { + return new GroupRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getName(); + } + + @Override + public RoleMappingResource roleMappingResource(Keycloak adminClient) { + throw new UnsupportedOperationException(); + } + + public GroupResource resource(Keycloak adminClient) { + return getRealm().resource(adminClient).groups().group(getIdAndReadIfNull(adminClient)); + } + + @Override + public GroupRepresentation read(Keycloak adminClient) { + return getRealm().resource(adminClient).groups().groups(getRepresentation().getName(), 0, 1).get(0); + } + + @Override + public Response create(Keycloak adminClient) { + return getRealm().resource(adminClient).groups().add(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Realm.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Realm.java new file mode 100644 index 0000000000..2dd2fe8737 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Realm.java @@ -0,0 +1,119 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + */ +public class Realm extends NestedEntity + implements Creatable { + + private List clients; + private List realmRoles; + private List users; + private List groups; + + private List clientRoles; // all clients' roles + private List resourceServers; // filtered clients + + public Realm(Dataset dataset, int index) { + super(dataset, index); + } + + @Override + public RealmRepresentation newRepresentation() { + return new RealmRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getRealm(); + } + + public Dataset getDataset() { + return getParentEntity(); + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + public List getClients() { + return clients; + } + + public void setClients(List clients) { + this.clients = clients; + } + + public List getRealmRoles() { + return realmRoles; + } + + public void setRealmRoles(List realmRoles) { + this.realmRoles = realmRoles; + } + + public List getClientRoles() { + return clientRoles; + } + + public void setClientRoles(List clientRoles) { + this.clientRoles = clientRoles; + } + + public List getResourceServers() { + return resourceServers; + } + + public void setResourceServers(List resourceServers) { + this.resourceServers = resourceServers; + } + + public RealmResource resource(Keycloak adminClient) { + return adminClient.realm(getRepresentation().getRealm()); + } + + @Override + public synchronized RealmRepresentation read(Keycloak adminClient) { + return adminClient.realms().realm(getRepresentation().getRealm()).toRepresentation(); + } + + @Override + public synchronized Response create(Keycloak adminClient) { + adminClient.realms().create(getRepresentation()); + return null; + } + + @Override + public synchronized void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public synchronized void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RealmRole.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RealmRole.java new file mode 100644 index 0000000000..e03df5ad0c --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RealmRole.java @@ -0,0 +1,37 @@ +package org.keycloak.performance.dataset.idm; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleByIdResource; +import org.keycloak.admin.client.resource.RolesResource; +import org.keycloak.representations.idm.RoleRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RealmRole extends Role { + + public RealmRole(Realm realm, int index) { + super(realm, index); + } + + public Realm getRealm() { + return getParentEntity(); + } + + @Override + public RolesResource rolesResource(Keycloak adminClient) { + return getRealm().resource(adminClient).roles(); + } + + @Override + public RoleByIdResource roleByIdResource(Keycloak adminClient) { + return getRealm().resource(adminClient).rolesById(); + } + + @Override + public RoleRepresentation newRepresentation() { + return new RoleRepresentation(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Role.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Role.java new file mode 100644 index 0000000000..6fd30b8421 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Role.java @@ -0,0 +1,63 @@ +package org.keycloak.performance.dataset.idm; + +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleByIdResource; +import org.keycloak.admin.client.resource.RoleResource; +import org.keycloak.admin.client.resource.RolesResource; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + * @param + */ +public abstract class Role extends NestedEntity + implements Creatable { + + public Role(PE parentEntity, int index) { + super(parentEntity, index); + } + + @Override + public RoleRepresentation newRepresentation() { + return new RoleRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getName(); + } + + public abstract RolesResource rolesResource(Keycloak adminClient); + + public abstract RoleByIdResource roleByIdResource(Keycloak adminClient); + + public RoleResource resource(Keycloak adminClient) { + return rolesResource(adminClient).get(getRepresentation().getName()); + } + + @Override + public RoleRepresentation read(Keycloak adminClient) { + return resource(adminClient).toRepresentation(); + } + + @Override + public Response create(Keycloak adminClient) { // FIXME + rolesResource(adminClient).create(getRepresentation()); + return null; + } + + @Override + public void update(Keycloak adminClient) { + roleByIdResource(adminClient).updateRole(getId(), getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + roleByIdResource(adminClient).deleteRole(getId()); + } +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMapper.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMapper.java new file mode 100644 index 0000000000..a86e2cd20b --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMapper.java @@ -0,0 +1,23 @@ +package org.keycloak.performance.dataset.idm; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleMappingResource; +import org.keycloak.performance.dataset.NestedEntity; + +/** + * + * @author tkyjovsk + */ +public abstract class RoleMapper extends NestedEntity { + + public RoleMapper(Realm realm, int index) { + super(realm, index); + } + + public Realm getRealm() { + return getParentEntity(); + } + + public abstract RoleMappingResource roleMappingResource(Keycloak adminClient); + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappings.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappings.java new file mode 100644 index 0000000000..3cf061cc05 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappings.java @@ -0,0 +1,49 @@ +package org.keycloak.performance.dataset.idm; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleScopeResource; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.performance.dataset.Updatable; + +/** + * + * @author tkyjovsk + * @param role-mapper parent entity (user or group) + */ +public class RoleMappings extends NestedEntity + implements Updatable { + + public RoleMappings(RM roleMapper, RoleMappingsRepresentation representation) { + super(roleMapper); + setRepresentation(representation); + } + + @Override + public RoleMappingsRepresentation newRepresentation() { + return new RoleMappingsRepresentation(); + } + + public RoleMapper getRoleMapper() { + return getParentEntity(); + } + + @Override + public String toString() { + return String.format("%s/role-mappings/realm", getRoleMapper()); + } + + public RoleScopeResource resource(Keycloak adminClient) { + return getRoleMapper().roleMappingResource(adminClient).realmLevel(); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).add(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(getRepresentation()); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappingsRepresentation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappingsRepresentation.java new file mode 100644 index 0000000000..6526ba7ad3 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappingsRepresentation.java @@ -0,0 +1,12 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.LinkedList; +import org.keycloak.representations.idm.RoleRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RoleMappingsRepresentation extends LinkedList { + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/User.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/User.java new file mode 100644 index 0000000000..3d075d9840 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/User.java @@ -0,0 +1,101 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.Iterator; +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleMappingResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.performance.dataset.Creatable; +import org.keycloak.performance.iteration.FilteredIterator; +import org.keycloak.performance.iteration.RandomIterator; + +/** + * + * @author tkyjovsk + */ +public class User extends RoleMapper implements Creatable { + + private List credentials; + private RoleMappings realmRoleMappings; + private List> clientRoleMappingsList; + + public User(Realm realm, int index) { + super(realm, index); + } + + @Override + public UserRepresentation newRepresentation() { + return new UserRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getUsername(); + } + + public void setRealmRoleMappings(RoleMappings realmRoleMappings) { + this.realmRoleMappings = realmRoleMappings; + } + + public void setClientRoleMappingsList(List> clientRoleMappingsList) { + this.clientRoleMappingsList = clientRoleMappingsList; + } + + public RoleMappings getRealmRoleMappings() { + return realmRoleMappings; + } + + public List> getClientRoleMappingsList() { + return clientRoleMappingsList; + } + + public List getCredentials() { + return credentials; + } + + public void setCredentials(List credentials) { + this.credentials = credentials; + } + + public UserResource resource(Keycloak adminClient) { + return getRealm().resource(adminClient).users().get(getIdAndReadIfNull(adminClient)); + } + + @Override + public UserRepresentation read(Keycloak adminClient) { + return getRealm().resource(adminClient).users().search(getRepresentation().getUsername()).get(0); + } + + @Override + public Response create(Keycloak adminClient) { + return getRealm().resource(adminClient).users().create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + + @Override + public RoleMappingResource roleMappingResource(Keycloak adminClient) { + return resource(adminClient).roles(); + } + + public Iterator randomClientIterator() { + return new RandomIterator<>(getRealm().getClients()); + } + + public Iterator randomConfidentialClientIterator() { + return new FilteredIterator<>(new RandomIterator<>(getRealm().getClients()), + c -> !c.getRepresentation().isPublicClient() && !c.getRepresentation().isBearerOnly() + ); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ClientPolicy.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ClientPolicy.java new file mode 100644 index 0000000000..a2673b655c --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ClientPolicy.java @@ -0,0 +1,64 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientPoliciesResource; +import org.keycloak.admin.client.resource.ClientPolicyResource; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ClientPolicy extends Policy { + + private List clients; + + public ClientPolicy(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public ClientPolicyRepresentation newRepresentation() { + return new ClientPolicyRepresentation(); + } + + public List getClients() { + return clients; + } + + public void setClients(List clients) { + this.clients = clients; + } + + public ClientPoliciesResource clientPoliciesResource(Keycloak adminClient) { + return policies(adminClient).client(); + } + + public ClientPolicyResource resource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).policies().client().findById(getIdAndReadIfNull(adminClient)); + } + + @Override + public ClientPolicyRepresentation read(Keycloak adminClient) { + return clientPoliciesResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return clientPoliciesResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/JsPolicy.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/JsPolicy.java new file mode 100644 index 0000000000..e2cae17fca --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/JsPolicy.java @@ -0,0 +1,52 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.JSPoliciesResource; +import org.keycloak.admin.client.resource.JSPolicyResource; +import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class JsPolicy extends Policy { + + public JsPolicy(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public JSPolicyRepresentation newRepresentation() { + return new JSPolicyRepresentation(); + } + + public JSPoliciesResource jsPoliciesResource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).policies().js(); + } + + public JSPolicyResource resource(Keycloak adminClient) { + return jsPoliciesResource(adminClient).findById(getIdAndReadIfNull(adminClient)); + } + + @Override + public JSPolicyRepresentation read(Keycloak adminClient) { + return jsPoliciesResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return jsPoliciesResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Policy.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Policy.java new file mode 100644 index 0000000000..f0b34337d9 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Policy.java @@ -0,0 +1,34 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.PoliciesResource; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + */ +public abstract class Policy + extends NestedEntity + implements Creatable { + + public Policy(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public String toString() { + return getRepresentation().getName(); + } + + public ResourceServer getResourceServer() { + return getParentEntity(); + } + + public PoliciesResource policies(Keycloak adminClient) { + return getResourceServer().resource(adminClient).policies(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Resource.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Resource.java new file mode 100644 index 0000000000..85d891cb72 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Resource.java @@ -0,0 +1,79 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.apache.commons.lang.Validate; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ResourceResource; +import org.keycloak.admin.client.resource.ResourcesResource; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + */ +public class Resource extends NestedEntity + implements Creatable { + + private List scopes; + + public Resource(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public ResourceRepresentation newRepresentation() { + return new ResourceRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getName(); + } + + public ResourceServer getResourceServer() { + return getParentEntity(); + } + + public ResourcesResource resourcesResource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).resources(); + } + + public ResourceResource resource(Keycloak adminClient) { + return resourcesResource(adminClient).resource(getIdAndReadIfNull(adminClient)); + } + + @Override + public ResourceRepresentation read(Keycloak adminClient) { + return resourcesResource(adminClient).findByName(getRepresentation().getName()).get(0); + } + + @Override + public Response create(Keycloak adminClient) { + Validate.notNull(getResourceServer()); + Validate.notNull(getResourceServer().getClient()); + Validate.notNull(getResourceServer().getClient().getRepresentation().getBaseUrl()); + return resourcesResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + + public List getScopes() { + return scopes; + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourcePermission.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourcePermission.java new file mode 100644 index 0000000000..5392e7321d --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourcePermission.java @@ -0,0 +1,72 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ResourcePermissionResource; +import org.keycloak.admin.client.resource.ResourcePermissionsResource; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ResourcePermission extends Policy { + + private List resources; + private List policies; + + public ResourcePermission(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public ResourcePermissionRepresentation newRepresentation() { + return new ResourcePermissionRepresentation(); + } + + public List getResources() { + return resources; + } + + public void setResources(List resources) { + this.resources = resources; + } + + public List getPolicies() { + return policies; + } + + public void setPolicies(List policies) { + this.policies = policies; + } + + public ResourcePermissionsResource resourcePermissionsResource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).permissions().resource(); + } + + public ResourcePermissionResource resource(Keycloak adminClient) { + return resourcePermissionsResource(adminClient).findById(getIdAndReadIfNull(adminClient)); + } + + @Override + public ResourcePermissionRepresentation read(Keycloak adminClient) { + return resourcePermissionsResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return resourcePermissionsResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServer.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServer.java new file mode 100644 index 0000000000..503bfc8580 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServer.java @@ -0,0 +1,148 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.AuthorizationResource; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.performance.dataset.Updatable; + +/** + * + * @author tkyjovsk + */ +public class ResourceServer extends NestedEntity + implements Updatable { + + private List scopes; + private List resources; + private List rolePolicies; + private List jsPolicies; + private List userPolicies; + private List clientPolicies; + private List resourcePermissions; + private List scopePermissions; + + private List allPolicies; + + public ResourceServer(Client client) { + super(client); + } + + @Override + public ResourceServerRepresentation newRepresentation() { + return new ResourceServerRepresentation(); + } + + public Client getClient() { + return getParentEntity(); + } + + @Override + public ResourceServerRepresentation getRepresentation() { + ResourceServerRepresentation r = super.getRepresentation(); + r.setId(getClient().getRepresentation().getId()); + r.setClientId(getClient().getRepresentation().getClientId()); + r.setName(getClient().getRepresentation().getName()); + return r; + } + + @Override + public String getId() { + return getClient().getId(); + } + + @Override + public String toString() { + return getClient().toString(); + } + + public List getResources() { + return resources; + } + + public void setResources(List resources) { + this.resources = resources; + } + + public List getScopes() { + return scopes; + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } + + public List getRolePolicies() { + return rolePolicies; + } + + public void setRolePolicies(List rolePolicies) { + this.rolePolicies = rolePolicies; + } + + public AuthorizationResource resource(Keycloak adminClient) { + return getClient().resource(adminClient).authorization(); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + getClient().delete(adminClient); + } + + public List getJsPolicies() { + return jsPolicies; + } + + public void setJsPolicies(List jsPolicies) { + this.jsPolicies = jsPolicies; + } + + public List getUserPolicies() { + return userPolicies; + } + + public void setUserPolicies(List userPolicies) { + this.userPolicies = userPolicies; + } + + public List getClientPolicies() { + return clientPolicies; + } + + public void setClientPolicies(List clientPolicies) { + this.clientPolicies = clientPolicies; + } + + public List getResourcePermissions() { + return resourcePermissions; + } + + public void setResourcePermissions(List resourcePermissions) { + this.resourcePermissions = resourcePermissions; + } + + public List getAllPolicies() { + return allPolicies; + } + + public void setAllPolicies(List allPolicies) { + this.allPolicies = allPolicies; + } + + public List getScopePermissions() { + return scopePermissions; + } + + public void setScopePermissions(List scopePermissions) { + this.scopePermissions = scopePermissions; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerList.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerList.java new file mode 100644 index 0000000000..3cc04f7951 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerList.java @@ -0,0 +1,46 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.AbstractList; +import java.util.List; +import static java.util.stream.Collectors.toList; +import org.keycloak.performance.dataset.idm.Client; + +/** + * + * @author tkyjovsk + */ +public class ResourceServerList extends AbstractList { + + List clients; + List resourceServers; + + public ResourceServerList(List clients) { + this.clients = clients; + } + + public void update() { + resourceServers = clients.stream() + .filter(c -> c.getRepresentation().getAuthorizationServicesEnabled()) + .map(c -> c.getResourceServer()) + .collect(toList()); + } + + public void updateIfNull() { + if (resourceServers == null) { + update(); + } + } + + @Override + public ResourceServer get(int index) { + updateIfNull(); + return resourceServers.get(index); + } + + @Override + public int size() { + updateIfNull(); + return resourceServers.size(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicy.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicy.java new file mode 100644 index 0000000000..f613338ba6 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicy.java @@ -0,0 +1,64 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RolePoliciesResource; +import org.keycloak.admin.client.resource.RolePolicyResource; +import org.keycloak.performance.dataset.idm.Role; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RolePolicy extends Policy { + + private List roles; + + public RolePolicy(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public RolePolicyRepresentation newRepresentation() { + return new RolePolicyRepresentation(); + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public RolePoliciesResource rolePoliciesResource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).policies().role(); + } + + public RolePolicyResource resource(Keycloak adminClient) { + return rolePoliciesResource(adminClient).findById(getIdAndReadIfNull(adminClient)); + } + + @Override + public RolePolicyRepresentation read(Keycloak adminClient) { + return rolePoliciesResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return rolePoliciesResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinition.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinition.java new file mode 100644 index 0000000000..03db5d2e75 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinition.java @@ -0,0 +1,22 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RolePolicyRoleDefinition extends NestedEntity { + + public RolePolicyRoleDefinition(RolePolicy parentEntity, int index, RolePolicyRepresentation.RoleDefinition representation) { + super(parentEntity, index); + setRepresentation(representation); + } + + @Override + public RolePolicyRepresentation.RoleDefinition newRepresentation() { + return new RolePolicyRepresentation.RoleDefinition(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinitionSet.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinitionSet.java new file mode 100644 index 0000000000..8d05ede488 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinitionSet.java @@ -0,0 +1,21 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.Collection; +import java.util.HashSet; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RolePolicyRoleDefinitionSet extends HashSet { + + public RolePolicyRoleDefinitionSet(Collection roleDefinitions) { + roleDefinitions.forEach(rd -> add( + new RolePolicyRepresentation.RoleDefinition( + rd.getRepresentation().getId(), + rd.getRepresentation().isRequired() + ))); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Scope.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Scope.java new file mode 100644 index 0000000000..c8d8a5b7f5 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Scope.java @@ -0,0 +1,63 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ResourceScopeResource; +import org.keycloak.admin.client.resource.ResourceScopesResource; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.performance.dataset.Creatable; + +/** + * + * @author tkyjovsk + */ +public class Scope extends NestedEntity + implements Creatable { + + public Scope(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public ScopeRepresentation newRepresentation() { + return new ScopeRepresentation(); + } + + @Override + public String toString() { + return getRepresentation().getName(); + } + + public ResourceServer getResourceServer() { + return getParentEntity(); + } + + public ResourceScopesResource scopesResource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).scopes(); + } + + public ResourceScopeResource resource(Keycloak adminClient) { + return scopesResource(adminClient).scope(getIdAndReadIfNull(adminClient)); + } + + @Override + public ScopeRepresentation read(Keycloak adminClient) { + return scopesResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return scopesResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ScopePermission.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ScopePermission.java new file mode 100644 index 0000000000..1f4a16fd57 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ScopePermission.java @@ -0,0 +1,72 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ScopePermissionResource; +import org.keycloak.admin.client.resource.ScopePermissionsResource; +import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ScopePermission extends Policy { + + private List scopes; + private List policies; + + public ScopePermission(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public ScopePermissionRepresentation newRepresentation() { + return new ScopePermissionRepresentation(); + } + + public ScopePermissionsResource scopePermissionsResource(Keycloak adminClient) { + return getResourceServer().resource(adminClient).permissions().scope(); + } + + public ScopePermissionResource resource(Keycloak adminClient) { + return scopePermissionsResource(adminClient).findById(getIdAndReadIfNull(adminClient)); + } + + @Override + public ScopePermissionRepresentation read(Keycloak adminClient) { + return scopePermissionsResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return scopePermissionsResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + + public List getPolicies() { + return policies; + } + + public void setPolicies(List policies) { + this.policies = policies; + } + + public List getScopes() { + return scopes; + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/UserPolicy.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/UserPolicy.java new file mode 100644 index 0000000000..2b160fc7f4 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/UserPolicy.java @@ -0,0 +1,64 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.UserPoliciesResource; +import org.keycloak.admin.client.resource.UserPolicyResource; +import org.keycloak.performance.dataset.idm.User; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class UserPolicy extends Policy { + + private List users; + + public UserPolicy(ResourceServer resourceServer, int index) { + super(resourceServer, index); + } + + @Override + public UserPolicyRepresentation newRepresentation() { + return new UserPolicyRepresentation(); + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public UserPoliciesResource userPoliciesResource(Keycloak adminClient) { + return policies(adminClient).user(); + } + + public UserPolicyResource resource(Keycloak adminClient) { + return userPoliciesResource(adminClient).findById(getIdAndReadIfNull(adminClient)); + } + + @Override + public UserPolicyRepresentation read(Keycloak adminClient) { + return userPoliciesResource(adminClient).findByName(getRepresentation().getName()); + } + + @Override + public Response create(Keycloak adminClient) { + return userPoliciesResource(adminClient).create(getRepresentation()); + } + + @Override + public void update(Keycloak adminClient) { + resource(adminClient).update(getRepresentation()); + } + + @Override + public void delete(Keycloak adminClient) { + resource(adminClient).remove(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/FilteredIterator.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/FilteredIterator.java similarity index 96% rename from testsuite/performance/tests/src/main/java/org/keycloak/performance/util/FilteredIterator.java rename to testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/FilteredIterator.java index f1f4dd378b..83e09761ce 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/FilteredIterator.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/FilteredIterator.java @@ -1,4 +1,4 @@ -package org.keycloak.performance.util; +package org.keycloak.performance.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/Flattened2DList.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/Flattened2DList.java new file mode 100644 index 0000000000..f0cdc6a500 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/Flattened2DList.java @@ -0,0 +1,34 @@ +package org.keycloak.performance.iteration; + +import java.util.AbstractList; +import java.util.List; + +/** + * 2D list of lists of the same size represented as a single list. + * Useful for proxying lists of nested entities for example client roles of clients. + * + * @author tkyjovsk + * @param type of X-list items + * @param type of Y-list items + */ +public abstract class Flattened2DList extends AbstractList { + + public abstract List getXList(); + + @Override + public int size() { + return getXList().size() * getYListSize(); + } + + @Override + public YT get(int index) { + int x = index % getXList().size(); + int y = index / getXList().size(); + return getYList(getXList().get(x)).get(y); + } + + public abstract List getYList(XT xList); + + public abstract int getYListSize(); + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/ListOfLists.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/ListOfLists.java new file mode 100644 index 0000000000..74a74055a7 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/ListOfLists.java @@ -0,0 +1,48 @@ +package org.keycloak.performance.iteration; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.lang.Validate; +import org.keycloak.performance.util.Loggable; + +/** + * + * @author tkyjovsk + */ +public class ListOfLists extends AbstractList implements Loggable { + + private final List> listOfLists = new LinkedList<>(); + + public ListOfLists(List> listOfLists) { + this.listOfLists.addAll(listOfLists); + } + + public ListOfLists(List... lists) { + this(Arrays.asList(lists)); + } + + @Override + public E get(int index) { + E e = null; + int rIndex = index; + for (List l : listOfLists) { + int s = l.size(); + if (s > rIndex) { + e = l.get(rIndex); + break; + } else { + rIndex -= s; + } + } + Validate.notNull(e); + return e; + } + + @Override + public int size() { + return listOfLists.stream().mapToInt(List::size).sum(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/LoopingIterator.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/LoopingIterator.java similarity index 93% rename from testsuite/performance/tests/src/main/java/org/keycloak/performance/util/LoopingIterator.java rename to testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/LoopingIterator.java index 15424cfd19..d28fcb187c 100644 --- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/LoopingIterator.java +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/LoopingIterator.java @@ -1,4 +1,4 @@ -package org.keycloak.performance.util; +package org.keycloak.performance.iteration; import java.util.Collection; import java.util.Iterator; diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomBooleans.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomBooleans.java new file mode 100644 index 0000000000..c4934dcaa1 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomBooleans.java @@ -0,0 +1,54 @@ +package org.keycloak.performance.iteration; + +import java.util.AbstractList; +import java.util.Collections; +import java.util.Map; +import org.apache.commons.collections.map.LRUMap; +import static org.keycloak.performance.iteration.RandomIntegers.RANDOMS_CACHE_SIZE; +import static org.keycloak.performance.iteration.RandomIntegers.getRandomIntegers; +import org.keycloak.performance.util.ValidateNumber; + +/** + * + * @author tkyjovsk + */ +public class RandomBooleans extends AbstractList { + + private final RandomIntegers randomIntegers; + private final int truesPercentage; + + /** + * + * @param seed Random sequence seed. + * @param truesPercentage Percentage of the sequence values which should be + * true. Valid range is 0-100. + */ + public RandomBooleans(int seed, int truesPercentage) { + randomIntegers = getRandomIntegers(seed, 100); + ValidateNumber.isInRange(truesPercentage, 0, 100); + this.truesPercentage = truesPercentage; + } + + public RandomBooleans(int seed) { + this(seed, 50); + } + + @Override + public Boolean get(int index) { + return randomIntegers.get(index) < truesPercentage; + } + + @Override + public int size() { + return Integer.MAX_VALUE; + } + + private static final Map RANDOM_BOOLS_CACHE + = Collections.synchronizedMap(new LRUMap(RANDOMS_CACHE_SIZE)); + + public static synchronized RandomBooleans getRandomBooleans(int seed, int percent) { + return RANDOM_BOOLS_CACHE + .computeIfAbsent(RandomIntegers.hashCode(seed, percent), (p) -> new RandomBooleans(seed, percent)); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIntegers.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIntegers.java new file mode 100644 index 0000000000..9c418946f2 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIntegers.java @@ -0,0 +1,96 @@ +package org.keycloak.performance.iteration; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Random; +import org.apache.commons.collections.map.LRUMap; +import org.keycloak.performance.util.ValidateNumber; + +/** + * + * @author tkyjovsk + */ +public class RandomIntegers extends AbstractList { + + protected final List randoms; + protected final int seed; + protected final int bound; + + private final Random random; + + public RandomIntegers(int seed, int bound) { + this.randoms = new ArrayList<>(); + this.seed = seed; + ValidateNumber.minValue(bound, 1); + this.bound = bound; + this.random = new Random(seed); + } + + protected int nextInt() { + return bound == 0 ? random.nextInt() : random.nextInt(bound); + } + + private void generateRandomsUpTo(int index) { + int mIndex = randoms.size() - 1; + for (int i = mIndex; i < index; i++) { + randoms.add(nextInt()); + } + } + + @Override + public Integer get(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(); + } + generateRandomsUpTo(index); + return randoms.get(index); + } + + @Override + public int size() { + return Integer.MAX_VALUE; + } + + @Override + public int hashCode() { + return hashCode(seed, bound); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RandomIntegers other = (RandomIntegers) obj; + if (this.seed != other.seed) { + return false; + } + return this.bound == other.bound; + } + + public static int hashCode(int seed, int bound) { + int hash = 5; + hash = 41 * hash + seed; + hash = 41 * hash + bound; + return hash; + } + + public static final int RANDOMS_CACHE_SIZE = Integer.parseInt(System.getProperty("randoms.cache.size", "10000")); + + private static final Map RANDOM_INTS_CACHE + = Collections.synchronizedMap(new LRUMap(RANDOMS_CACHE_SIZE)); + + public static synchronized RandomIntegers getRandomIntegers(int seed, int bound) { + return RANDOM_INTS_CACHE.computeIfAbsent(hashCode(seed, bound), r -> new RandomIntegers(seed, bound)); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIterator.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIterator.java new file mode 100644 index 0000000000..6072a84e98 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIterator.java @@ -0,0 +1,29 @@ +package org.keycloak.performance.iteration; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * + * @author tkyjovsk + */ +public class RandomIterator implements Iterator { + + List list; + + public RandomIterator(List iteratedList) { + this.list = iteratedList; + } + + @Override + public boolean hasNext() { + return true; + } + + @Override + public T next() { + return list.get(ThreadLocalRandom.current().nextInt(list.size())); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomSublist.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomSublist.java new file mode 100644 index 0000000000..d4e97cd5d0 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomSublist.java @@ -0,0 +1,43 @@ +package org.keycloak.performance.iteration; + +import java.util.AbstractList; +import java.util.List; +import static org.keycloak.performance.iteration.RandomIntegers.getRandomIntegers; +import static org.keycloak.performance.iteration.UniqueRandomIntegers.getUniqueRandomIntegers; + +/** + * + * @author tkyjovsk + * @param + */ +public class RandomSublist extends AbstractList { + + private final List originalList; + + private final List randomIndexesOfOriginalList; + + private final int size; + + public RandomSublist(List originalList, int seed, int sublistSize, boolean unique) { + this.originalList = originalList; + this.randomIndexesOfOriginalList = unique + ? getUniqueRandomIntegers(seed, originalList.size()) + : getRandomIntegers(seed, originalList.size()); + this.size = sublistSize; + } + + public RandomSublist(List originalList, int seed, int sublistSize) { + this(originalList, seed, sublistSize, false); + } + + @Override + public T get(int index) { + return originalList.get(randomIndexesOfOriginalList.get(index)); + } + + @Override + public int size() { + return size; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/UniqueRandomIntegers.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/UniqueRandomIntegers.java new file mode 100644 index 0000000000..6b4255f7a9 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/UniqueRandomIntegers.java @@ -0,0 +1,41 @@ +package org.keycloak.performance.iteration; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author tkyjovsk + */ +public class UniqueRandomIntegers extends RandomIntegers { + + private static final Map> UNIQUE_RANDOM_INTS_CACHE = new HashMap<>(); + + public UniqueRandomIntegers(int seed, int bound) { + super(seed, bound); + } + + @Override + protected int nextInt() { + int n = super.nextInt(); + return randoms.contains(n) ? nextInt() : n; + } + + @Override + public Integer get(int index) { + if (index >= bound) { + throw new IndexOutOfBoundsException(String.format( + "Sequence of unique random integers from interval [0,%s) only contains %s items. Requested index: %s, is out of bounds.", + bound, bound, index + )); + } + return super.get(index); + } + + public static synchronized UniqueRandomIntegers getUniqueRandomIntegers(int seed, int bound) { + return UNIQUE_RANDOM_INTS_CACHE + .computeIfAbsent(seed, (s) -> new HashMap<>()) + .computeIfAbsent(bound, (b) -> new UniqueRandomIntegers(seed, b)); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/DatasetTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/DatasetTemplate.java new file mode 100644 index 0000000000..51e5167807 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/DatasetTemplate.java @@ -0,0 +1,96 @@ +package org.keycloak.performance.templates; + +import java.io.File; +import java.util.List; +import org.apache.commons.configuration.CombinedConfiguration; +import org.keycloak.performance.templates.idm.RealmTemplate; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.lang.Validate; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.DatasetRepresentation; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.Realm; +import org.keycloak.performance.dataset.idm.User; +import org.keycloak.performance.iteration.Flattened2DList; +import org.keycloak.performance.util.CombinedConfigurationNoInterpolation; +import static org.keycloak.performance.util.ConfigurationUtil.loadFromFile; + +/** + * + * @author tkyjovsk + */ +public class DatasetTemplate extends EntityTemplate { + + protected final RealmTemplate realmTemplate; + + public DatasetTemplate(Configuration configuration) { + super(configuration); + this.realmTemplate = new RealmTemplate(this); + } + + public DatasetTemplate() { + this(loadConfiguration()); + } + + protected static Configuration loadConfiguration() { + try { + CombinedConfiguration configuration = new CombinedConfigurationNoInterpolation(); + String datasetPropertiesFile = System.getProperty("dataset.properties.file"); + Validate.notEmpty(datasetPropertiesFile); + configuration.addConfiguration(loadFromFile(new File(datasetPropertiesFile))); + return configuration; + } catch (ConfigurationException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public Dataset newEntity() { + return new Dataset(); + } + + @Override + public void processMappings(Dataset dataset) { + dataset.setRealms(new NestedEntityTemplateWrapperList<>(dataset, realmTemplate)); + dataset.setAllUsers(new Flattened2DList() { + @Override + public List getXList() { + return dataset.getRealms(); + } + + @Override + public List getYList(Realm realm) { + return realm.getUsers(); + } + + @Override + public int getYListSize() { + return realmTemplate.userTemplate.usersPerRealm; + } + }); + dataset.setAllClients(new Flattened2DList() { + @Override + public List getXList() { + return dataset.getRealms(); + } + + @Override + public List getYList(Realm realm) { + return realm.getClients(); + } + + @Override + public int getYListSize() { + return realmTemplate.clientTemplate.clientsPerRealm; + } + }); + } + + @Override + public void validateConfiguration() { + realmTemplate.validateConfiguration(); + logger().info(""); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityObjectWrapper.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityObjectWrapper.java new file mode 100644 index 0000000000..e48e61b743 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityObjectWrapper.java @@ -0,0 +1,35 @@ +package org.keycloak.performance.templates; + +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import java.util.Collections; +import java.util.Map; +import org.apache.commons.collections.map.LRUMap; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.NestedEntity; +import org.keycloak.performance.util.Loggable; + +/** + * + * @author tkyjovsk + */ +public class EntityObjectWrapper extends DefaultObjectWrapper implements Loggable { + + public static final int TEMPLATE_CACHE_SIZE = Integer.parseInt(System.getProperty("template.cache.size", "10000")); + public static final EntityObjectWrapper INSTANCE = new EntityObjectWrapper(); + + private final Map modelCache = Collections.synchronizedMap(new LRUMap(TEMPLATE_CACHE_SIZE)); + + @Override + protected TemplateModel handleUnknownType(Object obj) throws TemplateModelException { + if (obj instanceof NestedEntity) { + return modelCache.computeIfAbsent(obj, t -> new NestedEntityTemplateModel((NestedEntity) obj, modelCache)); + } + if (obj instanceof Entity) { + return modelCache.computeIfAbsent(obj, t -> new EntityTemplateModel((Entity) obj)); + } + return super.handleUnknownType(obj); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplate.java new file mode 100644 index 0000000000..d298af72f4 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplate.java @@ -0,0 +1,110 @@ +package org.keycloak.performance.templates; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.keycloak.performance.util.Loggable; +import org.keycloak.performance.dataset.Entity; +import static org.keycloak.performance.util.StringUtil.firstLetterToLowerCase; + +/** + * + * @author tkyjovsk + * @param entity + * @param representation + */ +public abstract class EntityTemplate, R> implements Loggable { + + public static final freemarker.template.Configuration FREEMARKER_CONFIG; + + public static final TypeReference MAP_TYPE_REFERENCE = new TypeReference>() { + }; + public static final ObjectMapper OBJECT_MAPPER; + + static { + FREEMARKER_CONFIG = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_26); + FREEMARKER_CONFIG.setBooleanFormat("true,false"); + FREEMARKER_CONFIG.setNumberFormat("computer"); + FREEMARKER_CONFIG.setObjectWrapper(EntityObjectWrapper.INSTANCE); + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + OBJECT_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + } + + private final Configuration configuration; + private final Map attributeTemplates = new LinkedHashMap<>(); + String configPrefix; + + public EntityTemplate(Configuration configuration) { + this.configuration = configuration; + this.configPrefix = firstLetterToLowerCase(this.getClass().getSimpleName().replaceFirst("Template$", "")); + registerAttributeTemplates(); + } + + public Configuration getConfiguration() { + return configuration; + } + + private void registerAttributeTemplates() { + Iterator configKeys = getConfiguration().getKeys(configPrefix); + while (configKeys.hasNext()) { + String configKey = configKeys.next(); + String attributeName = configKey.replaceFirst(configPrefix + ".", ""); + String attributeTemplateDefinition = getConfiguration().getString(configKey); + logger().trace("template: " + configPrefix + " -> " + attributeName + ": " + attributeTemplateDefinition); + try { + attributeTemplates.put(attributeName, new Template(configKey, attributeTemplateDefinition, FREEMARKER_CONFIG)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + + protected void processAtributeTemplates(E entity) { + Map updateMap = new HashMap<>(); + attributeTemplates.keySet().forEach((attributeName) -> { + updateMap.clear(); + try (StringWriter stringWriter = new StringWriter()) { + logger().trace("processing template for " + attributeName); + attributeTemplates.get(attributeName).process(entity, stringWriter); + updateMap.put(attributeName, stringWriter.toString()); + OBJECT_MAPPER.updateValue(entity.getRepresentation(), updateMap); + } catch (IOException | TemplateException ex) { + throw new RuntimeException(ex); + } + }); + } + + public E processEntity(E entity) { + processAttributes(entity); + processMappings(entity); + return entity; + } + + public synchronized E produce() { + return processEntity(newEntity()); + } + + public abstract E newEntity(); + + public E processAttributes(E entity) { + processAtributeTemplates(entity); + return entity; + } + + public abstract void processMappings(E entity); + + public abstract void validateConfiguration(); + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplateModel.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplateModel.java new file mode 100644 index 0000000000..a1690375e3 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplateModel.java @@ -0,0 +1,88 @@ +package org.keycloak.performance.templates; + +import freemarker.template.AdapterTemplateModel; +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapperBuilder; +import freemarker.template.ObjectWrapper; +import freemarker.template.TemplateHashModel; +import freemarker.template.TemplateHashModelEx; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateModelIterator; +import org.apache.commons.lang.Validate; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.util.Loggable; + +/** + * Merges template models of entity object and representation into a single + * model. + * + * @author tkyjovsk + */ +public class EntityTemplateModel implements TemplateHashModel, AdapterTemplateModel, Loggable { + + private static final DefaultObjectWrapperBuilder DOWB = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_26); + private static final ObjectWrapper DEFAULT_OBJECT_WRAPPER = DOWB.build(); + + private Entity entity; + private TemplateHashModel entityModel; + private TemplateHashModel representationModel; + + public EntityTemplateModel(Entity entity) { +// logger().debug("model for: " + entity.simpleClassName() + ", r: " + entity.getRepresentation().getClass().getSimpleName()); + try { + Validate.notNull(entity); + this.entity = entity; + this.entityModel = (TemplateHashModel) DEFAULT_OBJECT_WRAPPER.wrap(entity); + this.representationModel = (TemplateHashModel) DEFAULT_OBJECT_WRAPPER.wrap(entity.getRepresentation()); + } catch (TemplateModelException ex) { + throw new RuntimeException(ex); + } + } + + public Entity getEntity() { + return entity; + } + + @Override + public TemplateModel get(String key) throws TemplateModelException { + TemplateModel m = null; + if (m == null) { + m = representationModel.get(key); + } + if (m == null) { + m = entityModel.get(key); + } + if (m == null) { + logger().error("key " + key + " not found for entity: " + entity); + } + return m; + } + + @Override + public boolean isEmpty() throws TemplateModelException { + return entityModel.isEmpty() && representationModel.isEmpty(); + } + + @Override + public Entity getAdaptedObject(Class hint) { + return entity; + } + + public static String modelExToString(TemplateHashModelEx thme) { + StringBuilder sb = new StringBuilder(); + TemplateModelIterator i; + try { + i = thme.keys().iterator(); + while (i.hasNext()) { + TemplateModel k = i.next(); + TemplateModel v = thme.get(k.toString()); + sb.append(" - ").append(k).append("=").append(v).append('\n'); + } + } catch (TemplateModelException ex) { + throw new RuntimeException(ex); + } + return sb.toString(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplate.java new file mode 100644 index 0000000000..a3310b40a1 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplate.java @@ -0,0 +1,61 @@ +package org.keycloak.performance.templates; + +import java.util.Collections; +import java.util.Map; +import org.apache.commons.collections.map.LRUMap; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.NestedEntity; + +/** + * + * @author tkyjovsk + * @param + * @param + * @param + */ +public abstract class NestedEntityTemplate, R> + extends EntityTemplate { + + private final EntityTemplate parentEntityTemplate; + + public static final int ENTITY_CACHE_SIZE = Integer.parseInt(System.getProperty("entity.cache.size", "100000")); + + private final Map cache = Collections.synchronizedMap(new LRUMap(ENTITY_CACHE_SIZE)); + + public NestedEntityTemplate(EntityTemplate parentEntityTemplate) { + super(parentEntityTemplate.getConfiguration()); + this.parentEntityTemplate = parentEntityTemplate; + } + + public EntityTemplate getParentEntityTemplate() { + return parentEntityTemplate; + } + + public abstract int getEntityCountPerParent(); + + public abstract NE newEntity(PE parentEntity, int index); + + public NE newEntity(PE parentEntity) { + return newEntity(parentEntity, 0); + } + + @Override + public NE newEntity() { + throw new UnsupportedOperationException("Nested entity must have a parent entity."); + } + + public NE produce(PE parentEntity, int index) { + int entityHashcode = configPrefix.hashCode() * index + parentEntity.hashCode(); + return cache.computeIfAbsent(entityHashcode, e -> processEntity(newEntity(parentEntity, index))); + } + + public NE produce(PE parentEntity) { + return produce(parentEntity, 0); + } + + @Override + public NE produce() { + throw new UnsupportedOperationException("Nested entity must have a parent entity."); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateModel.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateModel.java new file mode 100644 index 0000000000..0ddf1e4731 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateModel.java @@ -0,0 +1,45 @@ +package org.keycloak.performance.templates; + +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import java.util.Map; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.NestedEntity; +import static org.keycloak.performance.util.StringUtil.firstLetterToLowerCase; + +/** + * + * @author tkyjovsk + */ +public class NestedEntityTemplateModel extends EntityTemplateModel { + + private TemplateModel parentEntityModel; + private String parentKey = null; + + public NestedEntityTemplateModel(NestedEntity entity, Map modelCache) { + super(entity); + try { + Entity parent = ((NestedEntity) entity).getParentEntity(); + this.parentEntityModel = (modelCache == null || !modelCache.containsKey(parent)) + ? EntityObjectWrapper.INSTANCE.wrap(parent) + : modelCache.get(parent); + this.parentKey = firstLetterToLowerCase(parent.simpleClassName()); + } catch (TemplateModelException ex) { + throw new RuntimeException(ex); + } + } + + public NestedEntityTemplateModel(NestedEntity entity) { + this(entity, null); + } + + @Override + public TemplateModel get(String key) throws TemplateModelException { + TemplateModel m = super.get(key); + if (key.equals(parentKey)) { + m = parentEntityModel; + } + return m; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateWrapperList.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateWrapperList.java new file mode 100644 index 0000000000..03bb6dcc99 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateWrapperList.java @@ -0,0 +1,35 @@ +package org.keycloak.performance.templates; + +import java.util.AbstractList; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.NestedEntity; + +/** + * A wrapper list for NestedEntityTemplate which delegates to the + template if requested element is absent in cache. + * + * @author tkyjovsk + * @param parent entity type + * @param child entity type + */ +public class NestedEntityTemplateWrapperList, R> extends AbstractList { + + PE parentEntity; + NestedEntityTemplate nestedEntityTemplate; + + public NestedEntityTemplateWrapperList(PE parentEntity, NestedEntityTemplate nestedEntityTemplate) { + this.parentEntity = parentEntity; + this.nestedEntityTemplate = nestedEntityTemplate; + } + + @Override + public int size() { + return nestedEntityTemplate.getEntityCountPerParent(); + } + + @Override + public NIE get(int index) { + return nestedEntityTemplate.produce(parentEntity, index); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringAttributeTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringAttributeTemplate.java new file mode 100644 index 0000000000..42f523be16 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringAttributeTemplate.java @@ -0,0 +1,21 @@ +package org.keycloak.performance.templates.attr; + +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.attr.StringAttribute; +import org.keycloak.performance.dataset.attr.StringAttributeRepresentation; +import org.keycloak.performance.templates.EntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplate; + +/** + * + * @author tkyjovsk + * @param owner entity + */ +public abstract class StringAttributeTemplate + extends NestedEntityTemplate, StringAttributeRepresentation> { + + public StringAttributeTemplate(EntityTemplate parentEntityTemplate) { + super(parentEntityTemplate); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringListAttributeTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringListAttributeTemplate.java new file mode 100644 index 0000000000..47669a09ab --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringListAttributeTemplate.java @@ -0,0 +1,30 @@ +package org.keycloak.performance.templates.attr; + +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.attr.StringListAttribute; +import org.keycloak.performance.dataset.attr.StringListAttributeRepresentation; +import org.keycloak.performance.templates.EntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplate; + +/** + * + * @author tkyjovsk + * @param owner entity + */ +public abstract class StringListAttributeTemplate + extends NestedEntityTemplate, StringListAttributeRepresentation> { + + public StringListAttributeTemplate(EntityTemplate parentEntityTemplate) { + super(parentEntityTemplate); + } + + @Override + public void processMappings(StringListAttribute entity) { + } + + @Override + public StringListAttribute newEntity(PE parentEntity, int index) { + return new StringListAttribute<>(parentEntity, index); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientRoleTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientRoleTemplate.java new file mode 100644 index 0000000000..70b9fefce0 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientRoleTemplate.java @@ -0,0 +1,50 @@ +package org.keycloak.performance.templates.idm; + +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.ClientRole; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.RoleRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ClientRoleTemplate extends NestedEntityTemplate { + + public static final String CLIENT_ROLES_PER_CLIENT = "clientRolesPerClient"; + + public final int clientRolesPerClient; + public final int clientRolesTotal; + + public ClientRoleTemplate(ClientTemplate clientTemplate) { + super(clientTemplate); + this.clientRolesPerClient = getConfiguration().getInt(CLIENT_ROLES_PER_CLIENT, 0); + this.clientRolesTotal = clientRolesPerClient * clientTemplate.clientsTotal; + } + + public ClientTemplate clientTemplate() { + return (ClientTemplate) getParentEntityTemplate(); + } + + @Override + public int getEntityCountPerParent() { + return clientRolesPerClient; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s, total: %s", CLIENT_ROLES_PER_CLIENT, clientRolesPerClient, clientRolesTotal)); + ValidateNumber.minValue(clientRolesPerClient, 0); + } + + @Override + public ClientRole newEntity(Client parentEntity, int index) { + return new ClientRole(parentEntity, index); + } + + @Override + public void processMappings(ClientRole entity) { + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientTemplate.java new file mode 100644 index 0000000000..0aae179e91 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientTemplate.java @@ -0,0 +1,67 @@ +package org.keycloak.performance.templates.idm; + +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.Realm; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplateWrapperList; +import org.keycloak.performance.templates.idm.authorization.ResourceServerTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.ClientRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ClientTemplate extends NestedEntityTemplate { + + public static final String CLIENTS_PER_REALM = "clientsPerRealm"; + + public final int clientsPerRealm; + public final int clientsTotal; + + public final ClientRoleTemplate clientRoleTemplate; + public final ResourceServerTemplate resourceServerTemplate; + + public ClientTemplate(RealmTemplate realmTemplate) { + super(realmTemplate); + + this.clientsPerRealm = getConfiguration().getInt(CLIENTS_PER_REALM, 0); + this.clientsTotal = clientsPerRealm * realmTemplate.realms; + + this.clientRoleTemplate = new ClientRoleTemplate(this); + this.resourceServerTemplate = new ResourceServerTemplate(this); + } + + public RealmTemplate realmTemplate() { + return (RealmTemplate) getParentEntityTemplate(); + } + + @Override + public int getEntityCountPerParent() { + return clientsPerRealm; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s, total: %s", CLIENTS_PER_REALM, clientsPerRealm, clientsTotal)); + ValidateNumber.minValue(clientsPerRealm, 0); + + clientRoleTemplate.validateConfiguration(); + resourceServerTemplate.validateConfiguration(); + } + + @Override + public Client newEntity(Realm parentEntity, int index) { + return new Client(parentEntity, index); + } + + @Override + public void processMappings(Client client) { + client.setClientRoles(new NestedEntityTemplateWrapperList<>(client, clientRoleTemplate)); + + if (client.getRepresentation().getAuthorizationServicesEnabled()) { + client.setResourceServer(resourceServerTemplate.produce(client)); + } + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/GroupTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/GroupTemplate.java new file mode 100644 index 0000000000..46b7ee22bb --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/GroupTemplate.java @@ -0,0 +1,85 @@ +package org.keycloak.performance.templates.idm; + +import org.keycloak.performance.dataset.attr.AttributeMap; +import org.keycloak.performance.dataset.idm.Group; +import org.keycloak.performance.dataset.idm.Realm; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplateWrapperList; +import org.keycloak.performance.templates.attr.StringListAttributeTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.GroupRepresentation; + +/** + * + * @author tkyjovsk + */ +public class GroupTemplate extends NestedEntityTemplate { + + public static final String GROUPS_PER_REALM = "groupsPerRealm"; + + public final int groupsPerRealm; + public final int groupsTotal; + + public final GroupAttributeTemplate attributeTemplate; + + public GroupTemplate(RealmTemplate realmTemplate) { + super(realmTemplate); + this.groupsPerRealm = getConfiguration().getInt(GROUPS_PER_REALM, 0); + this.groupsTotal = groupsPerRealm * realmTemplate.realms; + this.attributeTemplate = new GroupAttributeTemplate(); + } + + public RealmTemplate realmTemplate() { + return (RealmTemplate) getParentEntityTemplate(); + } + + @Override + public Group newEntity(Realm parentEntity, int index) { + return new Group(parentEntity, index); + } + + @Override + public void processMappings(Group group) { + group.getRepresentation().setAttributes(new AttributeMap(new NestedEntityTemplateWrapperList<>(group, attributeTemplate))); + } + + @Override + public int getEntityCountPerParent() { + return groupsPerRealm; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s, total: %s", GROUPS_PER_REALM, groupsPerRealm, groupsTotal)); + ValidateNumber.minValue(groupsPerRealm, 0); + + attributeTemplate.validateConfiguration(); + } + + public class GroupAttributeTemplate extends StringListAttributeTemplate { + + public static final String ATTRIBUTES_PER_GROUP = "attributesPerGroup"; + + public final int attributesPerGroup; + public final int attributesTotal; + + public GroupAttributeTemplate() { + super(GroupTemplate.this); + this.attributesPerGroup = getConfiguration().getInt(ATTRIBUTES_PER_GROUP, 0); + this.attributesTotal = attributesPerGroup * groupsTotal; + } + + @Override + public int getEntityCountPerParent() { + return attributesPerGroup; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", ATTRIBUTES_PER_GROUP, attributesPerGroup)); + ValidateNumber.minValue(attributesPerGroup, 0); + } + + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmRoleTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmRoleTemplate.java new file mode 100644 index 0000000000..10437590e4 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmRoleTemplate.java @@ -0,0 +1,50 @@ +package org.keycloak.performance.templates.idm; + +import org.keycloak.performance.dataset.idm.Realm; +import org.keycloak.performance.dataset.idm.RealmRole; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.RoleRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RealmRoleTemplate extends NestedEntityTemplate { + + public static final String REALM_ROLES_PER_REALM = "realmRolesPerRealm"; + + public final int realmRolesPerRealm; + public final int realmRolesTotal; + + public RealmRoleTemplate(RealmTemplate realmTemplate) { + super(realmTemplate); + this.realmRolesPerRealm = getConfiguration().getInt(REALM_ROLES_PER_REALM, 0); + this.realmRolesTotal = realmRolesPerRealm * realmTemplate.realms; + } + + public RealmTemplate realmTemplate() { + return (RealmTemplate) getParentEntityTemplate(); + } + + @Override + public int getEntityCountPerParent() { + return realmRolesPerRealm; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s, total: %s", REALM_ROLES_PER_REALM, realmRolesPerRealm, realmRolesTotal)); + ValidateNumber.minValue(realmRolesPerRealm, 0); + } + + @Override + public RealmRole newEntity(Realm parentEntity, int index) { + return new RealmRole(parentEntity, index); + } + + @Override + public void processMappings(RealmRole role) { + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmTemplate.java new file mode 100644 index 0000000000..150477060a --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmTemplate.java @@ -0,0 +1,87 @@ +package org.keycloak.performance.templates.idm; + +import java.util.List; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.ClientRole; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.iteration.Flattened2DList; +import org.keycloak.performance.dataset.idm.Realm; +import org.keycloak.performance.dataset.idm.authorization.ResourceServerList; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplateWrapperList; +import org.keycloak.performance.templates.DatasetTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.RealmRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RealmTemplate extends NestedEntityTemplate { + + public static final String REALMS = "realms"; + + public final int realms; + + public final ClientTemplate clientTemplate; + public final RealmRoleTemplate realmRoleTemplate; + public final UserTemplate userTemplate; + public final GroupTemplate groupTemplate; + + public RealmTemplate(DatasetTemplate datasetTemplate) { + super(datasetTemplate); + this.realms = getConfiguration().getInt(REALMS, 0); + this.clientTemplate = new ClientTemplate(this); + this.realmRoleTemplate = new RealmRoleTemplate(this); + this.userTemplate = new UserTemplate(this); + this.groupTemplate = new GroupTemplate(this); + } + + @Override + public int getEntityCountPerParent() { + return realms; + } + + @Override + public void validateConfiguration() { + // sizing + logger().info(String.format("%s: %s", REALMS, realms)); + ValidateNumber.minValue(realms, 0); + + clientTemplate.validateConfiguration(); + realmRoleTemplate.validateConfiguration(); + userTemplate.validateConfiguration(); + groupTemplate.validateConfiguration(); + } + + @Override + public Realm newEntity(Dataset parentEntity, int index) { + return new Realm(parentEntity, index); + } + + @Override + public void processMappings(Realm realm) { + realm.setClients(new NestedEntityTemplateWrapperList<>(realm, clientTemplate)); + realm.setResourceServers(new ResourceServerList(realm.getClients())); + realm.setClientRoles(new Flattened2DList() { + @Override + public List getXList() { + return realm.getClients(); + } + + @Override + public List getYList(Client client) { + return client.getClientRoles(); + } + + @Override + public int getYListSize() { + return clientTemplate.clientRoleTemplate.clientRolesPerClient; + } + }); + realm.setRealmRoles(new NestedEntityTemplateWrapperList<>(realm, realmRoleTemplate)); + realm.setUsers(new NestedEntityTemplateWrapperList<>(realm, userTemplate)); + realm.setGroups(new NestedEntityTemplateWrapperList<>(realm, groupTemplate)); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/UserTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/UserTemplate.java new file mode 100644 index 0000000000..89fef03e05 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/UserTemplate.java @@ -0,0 +1,184 @@ +package org.keycloak.performance.templates.idm; + +import java.util.LinkedList; +import java.util.List; +import static java.util.stream.Collectors.toList; +import org.keycloak.performance.dataset.attr.AttributeMap; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.ClientRole; +import org.keycloak.performance.dataset.idm.ClientRoleMappings; +import org.keycloak.performance.dataset.idm.Credential; +import org.keycloak.performance.dataset.idm.Realm; +import org.keycloak.performance.dataset.idm.RealmRole; +import org.keycloak.performance.dataset.idm.RoleMappings; +import org.keycloak.performance.dataset.idm.RoleMappingsRepresentation; +import org.keycloak.performance.dataset.idm.User; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplateWrapperList; +import org.keycloak.performance.templates.attr.StringListAttributeTemplate; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +/** + * + * @author tkyjovsk + */ +public class UserTemplate extends NestedEntityTemplate { + + public static final String USERS_PER_REALM = "usersPerRealm"; + public static final String REALM_ROLES_PER_USER = "realmRolesPerUser"; + public static final String CLIENT_ROLES_PER_USER = "clientRolesPerUser"; + + public final int usersPerRealm; + public final int usersTotal; + public final int realmRolesPerUser; + public final int clientRolesPerUser; + + public final UserAttributeTemplate attributeTemplate; + public final CredentialTemplate credentialTemplate; + + public UserTemplate(RealmTemplate realmTemplate) { + super(realmTemplate); + + this.usersPerRealm = getConfiguration().getInt(USERS_PER_REALM, 0); + this.usersTotal = usersPerRealm * realmTemplate.realms; + this.realmRolesPerUser = getConfiguration().getInt(REALM_ROLES_PER_USER, 0); + this.clientRolesPerUser = getConfiguration().getInt(CLIENT_ROLES_PER_USER, 0); + + this.attributeTemplate = new UserAttributeTemplate(); + this.credentialTemplate = new CredentialTemplate(); + } + + public RealmTemplate realmTemplate() { + return (RealmTemplate) getParentEntityTemplate(); + } + + @Override + public User newEntity(Realm parentEntity, int index) { + return new User(parentEntity, index); + } + + @Override + public void processMappings(User user) { + + user.setCredentials(new NestedEntityTemplateWrapperList<>(user, credentialTemplate)); + + // note: attributes are embedded in user rep. + user.getRepresentation().setAttributes(new AttributeMap(new NestedEntityTemplateWrapperList<>(user, attributeTemplate))); + + // REALM ROLE MAPPINGS + List realmRoles = new RandomSublist( + user.getRealm().getRealmRoles(), // original list + user.hashCode(), // random seed + realmRolesPerUser, // sublist size + false // unique randoms? + ); + RoleMappingsRepresentation rmr = new RoleMappingsRepresentation(); + realmRoles.forEach(rr -> rmr.add(rr.getRepresentation())); + user.setRealmRoleMappings(new RoleMappings<>(user, rmr)); + + // CLIENT ROLE MAPPINGS + List clientRoles = new RandomSublist( + user.getRealm().getClientRoles(), // original list + user.hashCode(), // random seed + clientRolesPerUser, // sublist size + false // unique randoms? + ); + + List> clientRoleMappingsList = new LinkedList<>(); + List clients = clientRoles.stream().map(ClientRole::getClient).distinct().collect(toList()); + clients.forEach(client -> { + List clientClientRoles = clientRoles.stream().filter(clientRole + -> client.equals(clientRole.getClient())) + .collect(toList()); + + RoleMappingsRepresentation cmr = new RoleMappingsRepresentation(); + clientClientRoles.forEach(cr -> cmr.add(cr.getRepresentation())); + + ClientRoleMappings crm = new ClientRoleMappings(user, client, cmr); + clientRoleMappingsList.add(crm); + }); + user.setClientRoleMappingsList(clientRoleMappingsList); + } + + @Override + public int getEntityCountPerParent() { + return usersPerRealm; + } + + @Override + public void validateConfiguration() { + + // sizing + logger().info(String.format("%s: %s, total: %s", USERS_PER_REALM, usersPerRealm, usersTotal)); + ValidateNumber.minValue(usersPerRealm, 0); + + // mappings + attributeTemplate.validateConfiguration(); + + logger().info(String.format("%s: %s", REALM_ROLES_PER_USER, realmRolesPerUser)); + ValidateNumber.isInRange(realmRolesPerUser, 0, + realmTemplate().realmRoleTemplate.realmRolesPerRealm); + + logger().info(String.format("%s: %s", CLIENT_ROLES_PER_USER, clientRolesPerUser)); + ClientTemplate ct = realmTemplate().clientTemplate; + ValidateNumber.isInRange(clientRolesPerUser, 0, + ct.clientsPerRealm * ct.clientRoleTemplate.clientRolesPerClient); + + } + + public class CredentialTemplate extends NestedEntityTemplate { + + public CredentialTemplate() { + super(UserTemplate.this); + } + + @Override + public int getEntityCountPerParent() { + return 1; + } + + @Override + public void validateConfiguration() { + } + + @Override + public Credential newEntity(User parentEntity, int index) { + return new Credential(parentEntity, index); + } + + @Override + public void processMappings(Credential entity) { + } + + } + + public class UserAttributeTemplate extends StringListAttributeTemplate { + + public static final String ATTRIBUTES_PER_USER = "attributesPerUser"; + + public final int attributesPerUser; + public final int attributesTotal; + + public UserAttributeTemplate() { + super(UserTemplate.this); + this.attributesPerUser = getConfiguration().getInt(ATTRIBUTES_PER_USER, 0); + this.attributesTotal = attributesPerUser * usersTotal; + } + + @Override + public int getEntityCountPerParent() { + return attributesPerUser; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", ATTRIBUTES_PER_USER, attributesPerUser)); + ValidateNumber.minValue(attributesPerUser, 0); + } + + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ClientPolicyTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ClientPolicyTemplate.java new file mode 100644 index 0000000000..9da91e6f27 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ClientPolicyTemplate.java @@ -0,0 +1,62 @@ +package org.keycloak.performance.templates.idm.authorization; + +import static java.util.stream.Collectors.toSet; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.ClientPolicy; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ClientPolicyTemplate extends PolicyTemplate { + + public static final String CLIENT_POLICIES_PER_RESOURCE_SERVER = "clientPoliciesPerResourceServer"; + public static final String CLIENTS_PER_CLIENT_POLICY = "clientsPerClientPolicy"; + + public final int clientPoliciesPerResourceServer; + public final int clientsPerClientPolicy; + + public ClientPolicyTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.clientPoliciesPerResourceServer = getConfiguration().getInt(CLIENT_POLICIES_PER_RESOURCE_SERVER, 0); + this.clientsPerClientPolicy = getConfiguration().getInt(CLIENTS_PER_CLIENT_POLICY, 0); + } + + @Override + public int getEntityCountPerParent() { + return clientPoliciesPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", CLIENT_POLICIES_PER_RESOURCE_SERVER, clientPoliciesPerResourceServer)); + ValidateNumber.minValue(clientPoliciesPerResourceServer, 0); + + logger().info(String.format("%s: %s", CLIENTS_PER_CLIENT_POLICY, clientsPerClientPolicy)); + ValidateNumber.isInRange(clientsPerClientPolicy, 0, + resourceServerTemplate().clientTemplate().realmTemplate().clientTemplate.clientsPerRealm); + } + + @Override + public ClientPolicy newEntity(ResourceServer parentEntity, int index) { + return new ClientPolicy(parentEntity, index); + } + + @Override + public void processMappings(ClientPolicy policy) { + policy.setClients(new RandomSublist<>( + policy.getResourceServer().getClient().getRealm().getClients(), // original list + policy.hashCode(), // random seed + clientsPerClientPolicy, // sublist size + false // unique randoms? + )); + policy.getRepresentation().setClients(policy.getClients() + .stream().map(u -> u.getId()) + .filter(id -> id != null) // need non-null policy IDs + .collect(toSet())); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/JsPolicyTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/JsPolicyTemplate.java new file mode 100644 index 0000000000..71def1b720 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/JsPolicyTemplate.java @@ -0,0 +1,47 @@ +package org.keycloak.performance.templates.idm.authorization; + +import org.keycloak.performance.dataset.idm.authorization.JsPolicy; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class JsPolicyTemplate extends PolicyTemplate { + + public static final String JS_POLICIES_PER_RESOURCE_SERVER = "jsPoliciesPerResourceServer"; + + public final int jsPoliciesPerResourceServer; + + public JsPolicyTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.jsPoliciesPerResourceServer = getConfiguration().getInt(JS_POLICIES_PER_RESOURCE_SERVER, 0); + } + + public int getJsPoliciesPerResourceServer() { + return jsPoliciesPerResourceServer; + } + + @Override + public int getEntityCountPerParent() { + return jsPoliciesPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", JS_POLICIES_PER_RESOURCE_SERVER, jsPoliciesPerResourceServer)); + ValidateNumber.minValue(jsPoliciesPerResourceServer, 0); + } + + @Override + public JsPolicy newEntity(ResourceServer parentEntity, int index) { + return new JsPolicy(parentEntity, index); + } + + @Override + public void processMappings(JsPolicy policy) { + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/PolicyTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/PolicyTemplate.java new file mode 100644 index 0000000000..a75654e87e --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/PolicyTemplate.java @@ -0,0 +1,23 @@ +package org.keycloak.performance.templates.idm.authorization; + +import org.keycloak.performance.dataset.idm.authorization.Policy; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public abstract class PolicyTemplate, R extends AbstractPolicyRepresentation> + extends NestedEntityTemplate { + + public PolicyTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + } + + public ResourceServerTemplate resourceServerTemplate() { + return (ResourceServerTemplate) getParentEntityTemplate(); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourcePermissionTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourcePermissionTemplate.java new file mode 100644 index 0000000000..4d021e76a6 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourcePermissionTemplate.java @@ -0,0 +1,81 @@ +package org.keycloak.performance.templates.idm.authorization; + +import static java.util.stream.Collectors.toSet; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.ResourcePermission; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ResourcePermissionTemplate extends PolicyTemplate { + + public static final String RESOURCE_PERMISSIONS_PER_RESOURCE_SERVER = "resourcePermissionsPerResourceServer"; + public static final String RESOURCES_PER_RESOURCE_PERMISSION = "resourcesPerResourcePermission"; + public static final String POLICIES_PER_RESOURCE_PERMISSION = "policiesPerResourcePermission"; + + public final int resourcePermissionsPerResourceServer; + public final int resourcesPerResourcePermission; + public final int policiesPerResourcePermission; + + public ResourcePermissionTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.resourcePermissionsPerResourceServer = getConfiguration().getInt(RESOURCE_PERMISSIONS_PER_RESOURCE_SERVER, 0); + this.resourcesPerResourcePermission = getConfiguration().getInt(RESOURCES_PER_RESOURCE_PERMISSION, 0); // should be 1 but that doesn't work with 0 resource servers + this.policiesPerResourcePermission = getConfiguration().getInt(POLICIES_PER_RESOURCE_PERMISSION, 0); + } + + @Override + public int getEntityCountPerParent() { + return resourcePermissionsPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", RESOURCE_PERMISSIONS_PER_RESOURCE_SERVER, resourcePermissionsPerResourceServer)); + ValidateNumber.minValue(resourcePermissionsPerResourceServer, 0); + + logger().info(String.format("%s: %s", RESOURCES_PER_RESOURCE_PERMISSION, resourcesPerResourcePermission)); + ValidateNumber.isInRange(resourcesPerResourcePermission, 0, resourceServerTemplate().resourceTemplate.resourcesPerResourceServer); // TODO should be >=1 but that doesn't work with 0 resource servers + + logger().info(String.format("%s: %s", POLICIES_PER_RESOURCE_PERMISSION, policiesPerResourcePermission)); + ValidateNumber.isInRange(policiesPerResourcePermission, 0, resourceServerTemplate().maxPolicies); + } + + @Override + public ResourcePermission newEntity(ResourceServer parentEntity, int index) { + return new ResourcePermission(parentEntity, index); + } + + @Override + public void processMappings(ResourcePermission permission) { + String resourceType = permission.getRepresentation().getResourceType(); + if (resourceType == null || "".equals(resourceType)) { + permission.setResources(new RandomSublist<>( + permission.getResourceServer().getResources(), // original list + permission.hashCode(), // random seed + resourcesPerResourcePermission, // sublist size + false // unique randoms? + )); + permission.getRepresentation().setResources( + permission.getResources().stream() + .map(r -> r.getId()).filter(id -> id != null).collect(toSet()) + ); + } + + permission.setPolicies(new RandomSublist<>( + permission.getResourceServer().getAllPolicies(), // original list + permission.hashCode(), // random seed + policiesPerResourcePermission, // sublist size + false // unique randoms? + )); + permission.getRepresentation().setPolicies(permission.getPolicies() + .stream().map(p -> p.getId()) + .filter(id -> id != null) // need non-null policy IDs + .collect(toSet())); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceServerTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceServerTemplate.java new file mode 100644 index 0000000000..07ac7d393b --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceServerTemplate.java @@ -0,0 +1,101 @@ +package org.keycloak.performance.templates.idm.authorization; + +import org.apache.commons.lang.Validate; +import org.keycloak.performance.templates.idm.*; +import org.keycloak.performance.dataset.idm.Client; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.iteration.ListOfLists; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplateWrapperList; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ResourceServerTemplate extends NestedEntityTemplate { + + public final ScopeTemplate scopeTemplate; + public final ResourceTemplate resourceTemplate; + public final RolePolicyTemplate rolePolicyTemplate; + public final JsPolicyTemplate jsPolicyTemplate; + public final UserPolicyTemplate userPolicyTemplate; + public final ClientPolicyTemplate clientPolicyTemplate; + public final ResourcePermissionTemplate resourcePermissionTemplate; + public final ScopePermissionTemplate scopePermissionTemplate; + + public final int maxPolicies; + + public ResourceServerTemplate(ClientTemplate clientTemplate) { + super(clientTemplate); + this.scopeTemplate = new ScopeTemplate(this); + this.resourceTemplate = new ResourceTemplate(this); + this.rolePolicyTemplate = new RolePolicyTemplate(this); + this.jsPolicyTemplate = new JsPolicyTemplate(this); + this.userPolicyTemplate = new UserPolicyTemplate(this); + this.clientPolicyTemplate = new ClientPolicyTemplate(this); + this.resourcePermissionTemplate = new ResourcePermissionTemplate(this); + this.scopePermissionTemplate = new ScopePermissionTemplate(this); + + this.maxPolicies = rolePolicyTemplate.rolePoliciesPerResourceServer + + jsPolicyTemplate.jsPoliciesPerResourceServer + + userPolicyTemplate.userPoliciesPerResourceServer + + clientPolicyTemplate.clientPoliciesPerResourceServer; + } + + public ClientTemplate clientTemplate() { + return (ClientTemplate) getParentEntityTemplate(); + } + + @Override + public void validateConfiguration() { + scopeTemplate.validateConfiguration(); + resourceTemplate.validateConfiguration(); + rolePolicyTemplate.validateConfiguration(); + jsPolicyTemplate.validateConfiguration(); + userPolicyTemplate.validateConfiguration(); + clientPolicyTemplate.validateConfiguration(); + resourcePermissionTemplate.validateConfiguration(); + scopePermissionTemplate.validateConfiguration(); + } + + @Override + public ResourceServer newEntity(Client client) { + Validate.notNull(client); + Validate.notNull(client.getRepresentation()); + Validate.notNull(client.getRepresentation().getBaseUrl()); + return new ResourceServer(client); + } + + @Override + public void processMappings(ResourceServer resourceServer) { + resourceServer.setScopes(new NestedEntityTemplateWrapperList<>(resourceServer, scopeTemplate)); + resourceServer.setResources(new NestedEntityTemplateWrapperList<>(resourceServer, resourceTemplate)); + + resourceServer.setRolePolicies(new NestedEntityTemplateWrapperList<>(resourceServer, rolePolicyTemplate)); + resourceServer.setJsPolicies(new NestedEntityTemplateWrapperList<>(resourceServer, jsPolicyTemplate)); + resourceServer.setUserPolicies(new NestedEntityTemplateWrapperList<>(resourceServer, userPolicyTemplate)); + resourceServer.setClientPolicies(new NestedEntityTemplateWrapperList<>(resourceServer, clientPolicyTemplate)); + resourceServer.setAllPolicies(new ListOfLists( // proxy list + resourceServer.getRolePolicies(), + resourceServer.getJsPolicies(), + resourceServer.getUserPolicies(), + resourceServer.getClientPolicies() + )); + + resourceServer.setResourcePermissions(new NestedEntityTemplateWrapperList<>(resourceServer, resourcePermissionTemplate)); + resourceServer.setScopePermissions(new NestedEntityTemplateWrapperList<>(resourceServer, scopePermissionTemplate)); + + } + + @Override + public int getEntityCountPerParent() { // parent is Client + return 1; + } + + @Override + public ResourceServer newEntity(Client parentEntity, int index) { + return newEntity(parentEntity); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceTemplate.java new file mode 100644 index 0000000000..0af6d00d9c --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceTemplate.java @@ -0,0 +1,72 @@ +package org.keycloak.performance.templates.idm.authorization; + +import java.util.List; +import static java.util.stream.Collectors.toSet; +import org.apache.commons.lang.Validate; +import org.keycloak.performance.dataset.idm.User; +import org.keycloak.performance.dataset.idm.authorization.Resource; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ResourceTemplate extends NestedEntityTemplate { + + public static final String RESOURCES_PER_RESOURCE_SERVER = "resourcesPerResourceServer"; + public static final String SCOPES_PER_RESOURCE = "scopesPerResource"; + + public final int resourcesPerResourceServer; + public final int scopesPerResource; + + public ResourceTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.resourcesPerResourceServer = getConfiguration().getInt(RESOURCES_PER_RESOURCE_SERVER, 0); + this.scopesPerResource = getConfiguration().getInt(SCOPES_PER_RESOURCE, 0); + } + + @Override + public int getEntityCountPerParent() { + return resourcesPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", RESOURCES_PER_RESOURCE_SERVER, resourcesPerResourceServer)); + ValidateNumber.minValue(resourcesPerResourceServer, 0); + + logger().info(String.format("%s: %s", SCOPES_PER_RESOURCE, scopesPerResource)); + ValidateNumber.isInRange(scopesPerResource, 0, + ((ResourceServerTemplate) getParentEntityTemplate()).scopeTemplate.scopesPerResourceServer); + } + + @Override + public Resource newEntity(ResourceServer parentEntity, int index) { + return new Resource(parentEntity, index); + } + + @Override + public void processMappings(Resource resource) { + + if (resource.getRepresentation().getOwnerManagedAccess()) { + List users = resource.getResourceServer().getClient().getRealm().getUsers(); + String ownerId = users.get(resource.indexBasedRandomInt(users.size())).getId(); // random user from the realm + Validate.notNull(ownerId, "Unable to assign user as owner of resource. Id not set."); + resource.getRepresentation().setOwner(ownerId); + } + + resource.setScopes(new RandomSublist<>( + resource.getResourceServer().getScopes(), // original list + resource.hashCode(), // random seed + scopesPerResource, // sublist size + false // unique randoms? + )); + resource.getRepresentation().setScopes( + resource.getScopes().stream().map(s -> s.getRepresentation()).collect(toSet())); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/RolePolicyTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/RolePolicyTemplate.java new file mode 100644 index 0000000000..2f78f90f98 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/RolePolicyTemplate.java @@ -0,0 +1,130 @@ +package org.keycloak.performance.templates.idm.authorization; + +import org.apache.commons.lang.Validate; +import org.keycloak.performance.dataset.Entity; +import org.keycloak.performance.dataset.idm.Role; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.RolePolicyRoleDefinition; +import org.keycloak.performance.dataset.idm.authorization.RolePolicy; +import org.keycloak.performance.dataset.idm.authorization.RolePolicyRoleDefinitionSet; +import org.keycloak.performance.iteration.ListOfLists; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.templates.NestedEntityTemplateWrapperList; +import org.keycloak.performance.templates.idm.ClientRoleTemplate; +import org.keycloak.performance.templates.idm.ClientTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class RolePolicyTemplate extends PolicyTemplate { + + public static final String ROLE_POLICIES_PER_RESOURCE_SERVER = "rolePoliciesPerResourceServer"; + + public final int rolePoliciesPerResourceServer; + + public final RolePolicyRoleDefinitionTemplate roleDefinitionTemplate; + + public RolePolicyTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.rolePoliciesPerResourceServer = getConfiguration().getInt(ROLE_POLICIES_PER_RESOURCE_SERVER, 0); + this.roleDefinitionTemplate = new RolePolicyRoleDefinitionTemplate(); + } + + @Override + public ResourceServerTemplate resourceServerTemplate() { + return (ResourceServerTemplate) getParentEntityTemplate(); + } + + @Override + public int getEntityCountPerParent() { + return rolePoliciesPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", ROLE_POLICIES_PER_RESOURCE_SERVER, rolePoliciesPerResourceServer)); + ValidateNumber.minValue(rolePoliciesPerResourceServer, 0); + + roleDefinitionTemplate.validateConfiguration(); + } + + @Override + public RolePolicy newEntity(ResourceServer parentEntity, int index) { + return new RolePolicy(parentEntity, index); + } + + @Override + public void processMappings(RolePolicy rolePolicy) { + rolePolicy.setRoles(new ListOfLists<>( + new RandomSublist( + rolePolicy.getResourceServer().getClient().getRealm().getRealmRoles(), // original list + rolePolicy.hashCode(), // random seed + roleDefinitionTemplate.realmRolesPerRolePolicy, // sublist size + false // unique randoms? + ), + new RandomSublist( + rolePolicy.getResourceServer().getClient().getRealm().getClientRoles(), // original list + rolePolicy.hashCode(), // random seed + roleDefinitionTemplate.clientRolesPerRolePolicy, // sublist size + false // unique randoms? + ) + )); + + rolePolicy.getRepresentation().setRoles(new RolePolicyRoleDefinitionSet( + new NestedEntityTemplateWrapperList<>(rolePolicy, roleDefinitionTemplate) + )); + } + + public class RolePolicyRoleDefinitionTemplate + extends NestedEntityTemplate { + + public static final String REALM_ROLES_PER_ROLE_POLICY = "realmRolesPerRolePolicy"; + public static final String CLIENT_ROLES_PER_ROLE_POLICY = "clientRolesPerRolePolicy"; + + public final int realmRolesPerRolePolicy; + public final int clientRolesPerRolePolicy; + + public RolePolicyRoleDefinitionTemplate() { + super(RolePolicyTemplate.this); + this.realmRolesPerRolePolicy = getConfiguration().getInt(REALM_ROLES_PER_ROLE_POLICY, 0); + this.clientRolesPerRolePolicy = getConfiguration().getInt(CLIENT_ROLES_PER_ROLE_POLICY, 0); + } + + @Override + public int getEntityCountPerParent() { + return realmRolesPerRolePolicy + clientRolesPerRolePolicy; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", REALM_ROLES_PER_ROLE_POLICY, realmRolesPerRolePolicy)); + logger().info(String.format("%s: %s", CLIENT_ROLES_PER_ROLE_POLICY, clientRolesPerRolePolicy)); + + ClientTemplate ct = resourceServerTemplate().clientTemplate(); + ClientRoleTemplate crt = ct.clientRoleTemplate; + int realmRolesMax = ct.realmTemplate().realmRoleTemplate.realmRolesPerRealm; + int clientRolesMax = ct.clientsPerRealm * crt.clientRolesPerClient; + + ValidateNumber.isInRange(realmRolesPerRolePolicy, 0, realmRolesMax); + ValidateNumber.isInRange(clientRolesPerRolePolicy, 0, clientRolesMax); + + } + + @Override + public RolePolicyRoleDefinition newEntity(RolePolicy rolePolicy, int index) { + Validate.isTrue(rolePolicy.getRoles().size() == getEntityCountPerParent()); + String roleUUID = ((Role) rolePolicy.getRoles().get(index)).getRepresentation().getId(); + return new RolePolicyRoleDefinition(rolePolicy, index, new RolePolicyRepresentation.RoleDefinition(roleUUID, false)); + } + + @Override + public void processMappings(RolePolicyRoleDefinition entity) { + } + + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopePermissionTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopePermissionTemplate.java new file mode 100644 index 0000000000..3c267c7c7f --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopePermissionTemplate.java @@ -0,0 +1,79 @@ +package org.keycloak.performance.templates.idm.authorization; + +import static java.util.stream.Collectors.toSet; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.ScopePermission; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ScopePermissionTemplate extends PolicyTemplate { + + public static final String SCOPE_PERMISSIONS_PER_RESOURCE_SERVER = "scopePermissionsPerResourceServer"; + public static final String SCOPES_PER_SCOPE_PERMISSION = "scopesPerScopePermission"; + public static final String POLICIES_PER_SCOPE_PERMISSION = "policiesPerScopePermission"; + + public final int scopePermissionsPerResourceServer; + public final int scopesPerScopePermission; + public final int policiesPerScopePermission; + + public ScopePermissionTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.scopePermissionsPerResourceServer = getConfiguration().getInt(SCOPE_PERMISSIONS_PER_RESOURCE_SERVER, 0); + this.scopesPerScopePermission = getConfiguration().getInt(SCOPES_PER_SCOPE_PERMISSION, 0); // should be 1 but that doesn't work with 0 resource servers + this.policiesPerScopePermission = getConfiguration().getInt(POLICIES_PER_SCOPE_PERMISSION, 0); + } + + @Override + public int getEntityCountPerParent() { + return scopePermissionsPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", SCOPE_PERMISSIONS_PER_RESOURCE_SERVER, scopePermissionsPerResourceServer)); + ValidateNumber.minValue(scopePermissionsPerResourceServer, 0); + + logger().info(String.format("%s: %s", SCOPES_PER_SCOPE_PERMISSION, scopesPerScopePermission)); + ValidateNumber.isInRange(scopesPerScopePermission, 0, resourceServerTemplate().scopeTemplate.scopesPerResourceServer); // TODO should be >=1 but that doesn't work with 0 resource servers + + logger().info(String.format("%s: %s", POLICIES_PER_SCOPE_PERMISSION, policiesPerScopePermission)); + ValidateNumber.isInRange(policiesPerScopePermission, 0, resourceServerTemplate().maxPolicies); + } + + @Override + public ScopePermission newEntity(ResourceServer parentEntity, int index) { + return new ScopePermission(parentEntity, index); + } + + @Override + public void processMappings(ScopePermission permission) { + + permission.setScopes(new RandomSublist<>( + permission.getResourceServer().getScopes(), // original list + permission.hashCode(), // random seed + scopesPerScopePermission, // sublist size + false // unique randoms? + )); + permission.getRepresentation().setScopes( + permission.getScopes().stream() + .map(r -> r.getId()).filter(id -> id != null).collect(toSet()) + ); + + permission.setPolicies(new RandomSublist<>( + permission.getResourceServer().getAllPolicies(), // original list + permission.hashCode(), // random seed + policiesPerScopePermission, // sublist size + false // unique randoms? + )); + permission.getRepresentation().setPolicies(permission.getPolicies() + .stream().map(p -> p.getId()) + .filter(id -> id != null) // need non-null policy IDs + .collect(toSet())); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopeTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopeTemplate.java new file mode 100644 index 0000000000..7b76acfa7d --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopeTemplate.java @@ -0,0 +1,44 @@ +package org.keycloak.performance.templates.idm.authorization; + +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.Scope; +import org.keycloak.performance.templates.NestedEntityTemplate; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; + +/** + * + * @author tkyjovsk + */ +public class ScopeTemplate extends NestedEntityTemplate { + + public static final String SCOPES_PER_RESOURCE_SERVER = "scopesPerResourceServer"; + + public final int scopesPerResourceServer; + + public ScopeTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.scopesPerResourceServer = getConfiguration().getInt(SCOPES_PER_RESOURCE_SERVER, 0); + } + + @Override + public int getEntityCountPerParent() { + return scopesPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", SCOPES_PER_RESOURCE_SERVER, scopesPerResourceServer)); + ValidateNumber.minValue(scopesPerResourceServer, 0); + } + + @Override + public Scope newEntity(ResourceServer parentEntity, int index) { + return new Scope(parentEntity, index); + } + + @Override + public void processMappings(Scope entity) { + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/UserPolicyTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/UserPolicyTemplate.java new file mode 100644 index 0000000000..42200de839 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/UserPolicyTemplate.java @@ -0,0 +1,62 @@ +package org.keycloak.performance.templates.idm.authorization; + +import static java.util.stream.Collectors.toSet; +import org.keycloak.performance.dataset.idm.authorization.ResourceServer; +import org.keycloak.performance.dataset.idm.authorization.UserPolicy; +import org.keycloak.performance.iteration.RandomSublist; +import org.keycloak.performance.util.ValidateNumber; +import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; + +/** + * + * @author tkyjovsk + */ +public class UserPolicyTemplate extends PolicyTemplate { + + public static final String USER_POLICIES_PER_RESOURCE_SERVER = "userPoliciesPerResourceServer"; + public static final String USERS_PER_USER_POLICY = "usersPerUserPolicy"; + + public final int userPoliciesPerResourceServer; + public final int usersPerUserPolicy; + + public UserPolicyTemplate(ResourceServerTemplate resourceServerTemplate) { + super(resourceServerTemplate); + this.userPoliciesPerResourceServer = getConfiguration().getInt(USER_POLICIES_PER_RESOURCE_SERVER, 0); + this.usersPerUserPolicy = getConfiguration().getInt(USERS_PER_USER_POLICY, 0); + } + + @Override + public int getEntityCountPerParent() { + return userPoliciesPerResourceServer; + } + + @Override + public void validateConfiguration() { + logger().info(String.format("%s: %s", USER_POLICIES_PER_RESOURCE_SERVER, userPoliciesPerResourceServer)); + ValidateNumber.minValue(userPoliciesPerResourceServer, 0); + + logger().info(String.format("%s: %s", USERS_PER_USER_POLICY, usersPerUserPolicy)); + ValidateNumber.isInRange(usersPerUserPolicy, 0, + resourceServerTemplate().clientTemplate().realmTemplate().userTemplate.usersPerRealm); + } + + @Override + public UserPolicy newEntity(ResourceServer parentEntity, int index) { + return new UserPolicy(parentEntity, index); + } + + @Override + public void processMappings(UserPolicy policy) { + policy.setUsers(new RandomSublist<>( + policy.getResourceServer().getClient().getRealm().getUsers(), // original list + policy.hashCode(), // random seed + usersPerUserPolicy, // sublist size + false // unique randoms? + )); + policy.getRepresentation().setUsers(policy.getUsers() + .stream().map(u -> u.getId()) + .filter(id -> id != null) // need non-null policy IDs + .collect(toSet())); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/CombinedConfigurationNoInterpolation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/CombinedConfigurationNoInterpolation.java new file mode 100644 index 0000000000..d325339337 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/CombinedConfigurationNoInterpolation.java @@ -0,0 +1,23 @@ +package org.keycloak.performance.util; + +import org.apache.commons.configuration.CombinedConfiguration; + +/** + * CombinedConfigurationNoInterpolation. This class disables variable interpolation (substution) + * because Freemarker which is used for entity templating uses the same syntax: ${property}. + * + * @author tkyjovsk + */ +public class CombinedConfigurationNoInterpolation extends CombinedConfiguration { + + @Override + protected Object interpolate(Object value) { + return value; + } + + @Override + protected String interpolate(String base) { + return base; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ConfigurationUtil.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ConfigurationUtil.java new file mode 100644 index 0000000000..b5955837a8 --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ConfigurationUtil.java @@ -0,0 +1,54 @@ +package org.keycloak.performance.util; + +import java.io.File; +import java.util.Iterator; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.jboss.logging.Logger; +import org.jboss.logging.Logger.Level; +import static org.jboss.logging.Logger.Level.INFO; + +/** + * + * @author tkyjovsk + */ +public class ConfigurationUtil { + + public static void logConfigurationState(Configuration c, Logger logger) { + logConfigurationState(c, logger, INFO); + } + + public static void logConfigurationState(Configuration c, Logger logger, Level logLevel) { + Iterator configKeys = c.getKeys(); + while (configKeys.hasNext()) { + String k = configKeys.next(); + logger.log(logLevel, String.format("Configuration: %s: %s", k, c.getProperty(k))); + } + } + + public static PropertiesConfiguration newPropertiesConfiguration() { + return newPropertiesConfiguration(false); + } + + public static PropertiesConfiguration newPropertiesConfiguration(boolean listParsing) { + PropertiesConfiguration configuration = new PropertiesConfiguration(); + configuration.setDelimiterParsingDisabled(!listParsing); + return configuration; + } + + public static PropertiesConfiguration loadFromFile(File file) throws ConfigurationException { + // this is needed to disable interpreting comma-delimited string properties as lists + return loadFromFile(file, false); + } + + public static PropertiesConfiguration loadFromFile(File file, boolean listParsing) throws ConfigurationException { + PropertiesConfiguration configuration = newPropertiesConfiguration(listParsing); + String path = file.isAbsolute() ? file.getParent() : null; + String filename = file.isAbsolute() ? file.getName() : file.getPath(); + configuration.setBasePath(path); + configuration.load(filename); + return configuration; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/Loggable.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/Loggable.java new file mode 100644 index 0000000000..07a85fc97d --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/Loggable.java @@ -0,0 +1,27 @@ +package org.keycloak.performance.util; + +import org.jboss.logging.Logger; + +/** + * + * @author tkyjovsk + */ +public interface Loggable { + + default public Logger logger() { + return Logger.getLogger(this.getClass()); + } + + default public void info(String message) { + logger().info(message); + } + + default public void debug(String message) { + logger().debug(message); + } + + default public void warn(String message) { + logger().warn(message); + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/StringUtil.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/StringUtil.java new file mode 100644 index 0000000000..fea709479e --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/StringUtil.java @@ -0,0 +1,32 @@ +package org.keycloak.performance.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author tkyjovsk + */ +public class StringUtil { + + public static String firstLetterToLowerCase(String string) { + return string.substring(0, 1).toLowerCase() + string.substring(1); + } + + public static String firstLetterToUpperCase(String string) { + return string.substring(0, 1).toUpperCase() + string.substring(1); + } + + public static List parseStringList(String string) { + return parseStringList(string, ","); + } + + public static List parseStringList(String string, String delimiter) { + List list = new ArrayList<>(); + for (String s : string.split(delimiter)) { + list.add(s.trim()); + } + return list; + } + +} diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ValidateNumber.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ValidateNumber.java new file mode 100644 index 0000000000..84831561ce --- /dev/null +++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ValidateNumber.java @@ -0,0 +1,45 @@ +package org.keycloak.performance.util; + +import org.apache.commons.validator.routines.IntegerValidator; + +/** + * + * @author tkyjovsk + */ +public class ValidateNumber { + + private static IntegerValidator validate() { + return IntegerValidator.getInstance(); + } + + public static void minValue(int i, int min, String message) { + if (!validate().minValue(i, min)) { + throw new IllegalArgumentException(String.format("Value '%s' lower than the expected minimum: %s. %s", i, min, message)); + } + } + + public static void minValue(int i, int min) { + minValue(i, min, ""); + } + + public static void maxValue(int i, int max, String message) { + if (!validate().maxValue(i, max)) { + throw new IllegalArgumentException(String.format("Value '%s' greater than the expected maximum: %s. %s", i, max, message)); + } + } + + public static void maxValue(int i, int max) { + maxValue(i, max, ""); + } + + public static void isInRange(int i, int min, int max, String message) { + if (!validate().isInRange(i, min, max)) { + throw new IllegalArgumentException(String.format("Value '%s' is outside of the expected range: <%s, %s>. %s", i, min, max, message)); + } + } + + public static void isInRange(int i, int min, int max) { + isInRange(i, min, max, ""); + } + +} diff --git a/testsuite/performance/tests/src/main/resources/logback.xml b/testsuite/performance/tests/src/main/resources/logback.xml index 153511f2e8..bf8fd926f6 100644 --- a/testsuite/performance/tests/src/main/resources/logback.xml +++ b/testsuite/performance/tests/src/main/resources/logback.xml @@ -12,13 +12,26 @@ false - - - - + + + %d{HH:mm:ss} %msg%n%rEx + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/AbstractTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/AbstractTest.java new file mode 100644 index 0000000000..a1d163e535 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/AbstractTest.java @@ -0,0 +1,13 @@ +package org.keycloak.performance; + +import org.jboss.logging.Logger; + +/** + * + * @author tkyjovsk + */ +public abstract class AbstractTest { + + protected final Logger logger = Logger.getLogger(this.getClass()); + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/DatasetTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/DatasetTest.java new file mode 100644 index 0000000000..a3568457d5 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/DatasetTest.java @@ -0,0 +1,109 @@ +package org.keycloak.performance.dataset; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.Map; +import static java.util.stream.Collectors.toList; +import java.util.stream.Stream; +import org.junit.Ignore; +import org.junit.Test; +import org.keycloak.performance.dataset.idm.Credential; +import org.keycloak.performance.dataset.idm.authorization.Resource; +import org.keycloak.performance.dataset.idm.authorization.ResourcePermission; +import org.keycloak.performance.templates.DatasetTemplate; +import org.keycloak.performance.util.Loggable; +import org.keycloak.representations.idm.RealmRepresentation; +import static org.keycloak.util.JsonSerialization.writeValueAsString; + +/** + * + * @author tkyjovsk + */ +public class DatasetTest extends EntityTest implements Loggable { + + DatasetTemplate dt = new DatasetTemplate(); + Dataset dataset = dt.produce(); + + @Test + @Ignore + public void toJSON() throws IOException { + logger().info("REALM JSON: \n" + dataset.getRealms().get(0).toJSON()); + logger().info("CLIENT JSON: \n" + dataset.getRealms().get(0).getClients().get(0).toJSON()); + logger().info("USER JSON: \n" + dataset.getRealms().get(0).getUsers().get(0).toJSON()); + logger().info("CREDENTIAL JSON: \n" + dataset.getRealms().get(0).getUsers().get(0).getCredentials().get(0).toJSON()); + logger().info("REALM ROLE MAPPINGS: \n" + dataset.getRealms().get(0).getUsers().get(0).getRealmRoleMappings().toJSON()); + + if (dataset.getRealms().get(0).getResourceServers().isEmpty()) { + } else { + logger().info("RESOURCE SERVER: \n" + dataset.getRealms().get(0).getResourceServers().get(0).toJSON()); + logger().info("RESOURCE: \n" + dataset.getRealms().get(0).getResourceServers().get(0).getResources().get(0).toJSON()); + } + + } + + @Test + @Ignore + public void pojoToMap() throws IOException { + RealmRepresentation realm = new RealmRepresentation(); + realm.setRealm("realm_0"); + realm.setEnabled(true); + + logger().info("REP JSON:"); + logger().info(writeValueAsString(realm)); + + TypeReference typeRef = new TypeReference>() { + }; + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_NULL); + Map map = mapper.convertValue(realm, typeRef); + map.put("index", 1000); + + logger().info("MAP:"); + logger().info(map); + + logger().info("MAP JSON:"); + logger().info(writeValueAsString(map)); + + } + + @Test + @Ignore + public void testStreams() throws IOException { + + dataset.realms().forEach(r -> logger().info(r.toString())); + dataset.realmRoles().forEach(rr -> logger().info(rr.toString())); + dataset.clients().forEach(c -> logger().info(c.toString())); + dataset.clientRoles().forEach(cr -> logger().info(cr.toString())); + dataset.users().forEach(u -> logger().info(u.toString())); + for (Credential c : dataset.credentials().collect(toList())) { + logger().info(c.toJSON()); + } + + dataset.resourceServers().forEach(rs -> logger().info(rs.toString())); + dataset.resources().forEach(r -> logger().info(r.toString())); + for (Resource r : dataset.resources().collect(toList())) { + logger().info(r.toJSON()); + } + for (ResourcePermission rp : dataset.resourcePermissions().collect(toList())) { + logger().info(rp.toString()); + logger().info(rp.toJSON()); + } + + } + + @Override + public void testHashCode() { + String d1sn = getD1().getClass().getSimpleName(); + String d2sn = getD2().getClass().getSimpleName(); + logger().info(String.format("'%s' - '%s' '%s' - '%s'", d1sn, d2sn, d1sn.hashCode(), d2sn.hashCode())); + super.testHashCode(); + } + + @Override + public Stream entityStream(Dataset dataset) { + return Stream.of(dataset); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/EntityTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/EntityTest.java new file mode 100644 index 0000000000..2f635975a2 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/EntityTest.java @@ -0,0 +1,101 @@ +package org.keycloak.performance.dataset; + +import java.util.Iterator; +import java.util.stream.Stream; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.performance.templates.DatasetTemplate; +import org.keycloak.performance.util.Loggable; + +/** + * + * @author tkyjovsk + */ +public abstract class EntityTest implements Loggable { + + private Dataset d1; + private Dataset d2; + + @Before + public void prepareDatasets() { + freemarker.template.Configuration fmc = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_26); + fmc.setBooleanFormat("true,false"); + fmc.setNumberFormat("computer"); + + DatasetTemplate dt = new DatasetTemplate(); + d1 = dt.produce(); +// logger().info("created: " + d1); + d2 = dt.produce(); +// logger().info("created: " + d2); + } + + public Dataset getD1() { + return d1; + } + + public Dataset getD2() { + return d2; + } + + public abstract Stream entityStream(Dataset dataset); + + public Stream d1EntityStream() { + return entityStream(getD1()); + } + + public Stream d2EntityStream() { + return entityStream(getD2()); + } + + @Test + public void testHashCode() { + logger().info(this.getClass().getSimpleName() + " testHashCode()"); + Iterator e1i = d1EntityStream().iterator(); + Iterator e2i = d2EntityStream().iterator(); + int checkCount = 0; + while (e1i.hasNext()) { + T e1 = e1i.next(); + assertTrue(e2i.hasNext()); + T e2 = e2i.next(); + +// logger().info(String.format("entities: %s %s", e1, e2)); +// logger().info(String.format("hashCodes: %s %s", e1.hashCode(), e2.hashCode())); + assertEquals(e1.hashCode(), e1.hashCode()); + assertEquals(e2.hashCode(), e2.hashCode()); + assertEquals(e1.hashCode(), e2.hashCode()); + + checkCount++; +// if (checkCount > 10) { +// break; +// } + } + logger().info("checkCount: " + checkCount); +// assertTrue(checkCount > 0); + } + + @Test + public void testEquals() { + logger().info(this.getClass().getSimpleName() + " testEquals()"); + Iterator e1i = d1EntityStream().iterator(); + Iterator e2i = d2EntityStream().iterator(); + int checkCount = 0; + while (e1i.hasNext()) { + T e1 = e1i.next(); + assertTrue(e2i.hasNext()); + T e2 = e2i.next(); +// logger().info(String.format("entities: %s %s", e1, e2)); + assertTrue(e1.equals(e2)); + assertTrue(e2.equals(e1)); + + checkCount++; +// if (checkCount > 10) { +// break; +// } + } + logger().info("checkCount: " + checkCount); +// assertTrue(checkCount > 0); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleMappingsTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleMappingsTest.java new file mode 100644 index 0000000000..fb56de9333 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleMappingsTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class ClientRoleMappingsTest extends EntityTest> { + + @Override + public Stream> entityStream(Dataset dataset) { + return dataset.userClientRoleMappings(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleTest.java new file mode 100644 index 0000000000..8311969776 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class ClientRoleTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.clientRoles(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientTest.java new file mode 100644 index 0000000000..31f221177e --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class ClientTest extends EntityTest{ + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.clients(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/CredentialTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/CredentialTest.java new file mode 100644 index 0000000000..860aba5740 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/CredentialTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class CredentialTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.credentials(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleMappingsTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleMappingsTest.java new file mode 100644 index 0000000000..bf3ae831db --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleMappingsTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class RealmRoleMappingsTest extends EntityTest> { + + @Override + public Stream> entityStream(Dataset dataset) { + return dataset.userRealmRoleMappings(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleTest.java new file mode 100644 index 0000000000..fad8c1f53b --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class RealmRoleTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.realmRoles(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmTest.java new file mode 100644 index 0000000000..674cbecce5 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class RealmTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.realms(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/UserTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/UserTest.java new file mode 100644 index 0000000000..614b811ae8 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/UserTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class UserTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.users(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerTest.java new file mode 100644 index 0000000000..b50bebc5cd --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class ResourceServerTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.resourceServers(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceTest.java new file mode 100644 index 0000000000..a22e8c13d9 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class ResourceTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.resources(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyTest.java new file mode 100644 index 0000000000..ba6220be17 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyTest.java @@ -0,0 +1,23 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.stream.Stream; +import org.junit.Test; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class RolePolicyTest extends EntityTest { + + @Test + public void testRoles() { + } + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.rolePolicies(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ScopeTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ScopeTest.java new file mode 100644 index 0000000000..837f5a071e --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ScopeTest.java @@ -0,0 +1,18 @@ +package org.keycloak.performance.dataset.idm.authorization; + +import java.util.stream.Stream; +import org.keycloak.performance.dataset.Dataset; +import org.keycloak.performance.dataset.EntityTest; + +/** + * + * @author tkyjovsk + */ +public class ScopeTest extends EntityTest { + + @Override + public Stream entityStream(Dataset dataset) { + return dataset.scopes(); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/ListOfListsTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/ListOfListsTest.java new file mode 100644 index 0000000000..b34791b781 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/ListOfListsTest.java @@ -0,0 +1,46 @@ +package org.keycloak.performance.iteration; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.performance.util.Loggable; + +/** + * + * @author tkyjovsk + */ +public class ListOfListsTest implements Loggable { + + int[] sizes; + List> lists; + List items; + + @Before + public void prepareLists() { + sizes = new Random().ints(10, 1, 10).toArray(); + lists = new LinkedList<>(); + items = new LinkedList<>(); + for (int l = 0; l < sizes.length; l++) { + List list = new LinkedList<>(); + for (int i = 0; i < sizes[l]; i++) { + list.add(String.format("list %s item %s", l, i)); + } + lists.add(list); + items.addAll(list); + } + } + + @Test + public void testSize() { + lists.forEach(l -> logger().debug(l)); + ListOfLists lol = new ListOfLists<>(lists); + assertEquals(items.size(), lol.size()); + for (int i = 0; i < items.size(); i++) { + assertEquals(items.get(i), lol.get(i)); + } + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomBooleansTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomBooleansTest.java new file mode 100644 index 0000000000..73e774736c --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomBooleansTest.java @@ -0,0 +1,47 @@ +package org.keycloak.performance.iteration; + +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.performance.AbstractTest; + +/** + * + * @author tkyjovsk + */ +public class RandomBooleansTest extends AbstractTest { + + Random r; + + @Before + public void before() { + r = new Random(); + } + + @Test +// @Ignore + public void testRandoms() { + int seed1 = r.nextInt(); + int seed2 = r.nextInt(); + + List list1 = new RandomBooleans(seed1, 50).stream().limit(10).collect(Collectors.toList()); + List list2 = new RandomBooleans(seed1, 50).stream().limit(10).collect(Collectors.toList()); + List list3 = new RandomBooleans(seed2, 50).stream().limit(10).collect(Collectors.toList()); + + logger.info(String.format("List1(seed: %s): %s", seed1, list1)); + logger.info(String.format("List2(seed: %s): %s", seed1, list2)); + logger.info(String.format("List3(seed: %s): %s", seed2, list3)); + + assertFalse(list1.isEmpty()); + assertFalse(list2.isEmpty()); + assertFalse(list3.isEmpty()); + assertEquals(list1, list2); + assertNotEquals(list2, list3); + } + +} diff --git a/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomIntegersTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomIntegersTest.java new file mode 100644 index 0000000000..f1991a85b9 --- /dev/null +++ b/testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomIntegersTest.java @@ -0,0 +1,76 @@ +package org.keycloak.performance.iteration; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.keycloak.performance.AbstractTest; + +/** + * + * @author tkyjovsk + */ +public class RandomIntegersTest extends AbstractTest { + + Random r; + + @Before + public void before() { + r = new Random(); + } + + @Test + @Ignore + public void testRandoms() { + int seed1 = r.nextInt(); + int seed2 = r.nextInt(); + + List l1 = new RandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList()); + List l2 = new RandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList()); + List l3 = new RandomIntegers(seed2, 20).stream().limit(10).collect(Collectors.toList()); + + logger.info("L1: " + l1); + logger.info("L2: " + l2); + logger.info("L3: " + l3); + + assertFalse(l1.isEmpty()); + assertFalse(l2.isEmpty()); + assertFalse(l3.isEmpty()); + assertEquals(l1, l2); + assertNotEquals(l2, l3); + } + + @Test + @Ignore + public void testUniqueRandoms() { + int seed1 = r.nextInt(); + int seed2 = r.nextInt(); + + List l1 = new UniqueRandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList()); + List l2 = new UniqueRandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList()); + List l3 = new UniqueRandomIntegers(seed2, 20).stream().limit(10).collect(Collectors.toList()); + + logger.info("unique L1: " + l1); + logger.info("unique L2: " + l2); + logger.info("unique L3: " + l3); + + assertFalse(l1.isEmpty()); + assertFalse(l2.isEmpty()); + assertFalse(l3.isEmpty()); + assertEquals(l1, l2); + assertNotEquals(l2, l3); + } + + @Test + @Ignore + public void testStream() { + ThreadLocalRandom.current().ints(0, 100).distinct().limit(5).forEach(System.out::println); + } + +} diff --git a/testsuite/performance/tests/src/test/resources/data/user_credentials.csv b/testsuite/performance/tests/src/test/resources/data/user_credentials.csv deleted file mode 100644 index adec979994..0000000000 --- a/testsuite/performance/tests/src/test/resources/data/user_credentials.csv +++ /dev/null @@ -1,11 +0,0 @@ -username,password -user1,password1 -user2,password2 -user3,password3 -user4,password4 -user5,password5 -user6,password6 -user7,password7 -user8,password8 -user9,password9 -user10,password10 \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/resources/data/user_information.csv b/testsuite/performance/tests/src/test/resources/data/user_information.csv deleted file mode 100644 index 86aaf4c33d..0000000000 --- a/testsuite/performance/tests/src/test/resources/data/user_information.csv +++ /dev/null @@ -1,11 +0,0 @@ -username,password,account_id -user1,password1,1 -user2,password2,6 -user3,password3,9 -user4,password4,12 -user5,password5,15 -user6,password6,18 -user7,password7,21 -user8,password8,24 -user9,password9,27 -user10,password10,30 \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/resources/dataset/100r_100c_100u.properties b/testsuite/performance/tests/src/test/resources/dataset/100r_100c_100u.properties new file mode 100644 index 0000000000..7ed6c778d3 --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/100r_100c_100u.properties @@ -0,0 +1,73 @@ +# REALM +realms=100 +realm.realm=realm_${index?string("00")} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(1) +#realm.passwordPolicy=hashIterations(27500) + +# REALM ROLE +realmRolesPerRealm=3 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=3 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +# TODO support for multiple uris +#client.redirectUris=${baseUrl}/* http://load-balancing-domain.test/${clientId}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=3 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=100 +user.username=user_${index?string("00")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +#userAttribute.value=value_of_${name} +#userAttribute.value=value0_of_${name},value1_of_${name},value2_of_${name} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_100c_100000u.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_100000u.properties new file mode 100644 index 0000000000..6bb948454c --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_100000u.properties @@ -0,0 +1,68 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(27500) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=100 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=100000 +user.username=user_${index?string("00000")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_100c_100000u_1hi.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_100000u_1hi.properties new file mode 100644 index 0000000000..69b9088214 --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_100000u_1hi.properties @@ -0,0 +1,68 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(1) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=100 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=100000 +user.username=user_${index?string("00000")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=0 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u.properties new file mode 100644 index 0000000000..71bbe0d17c --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u.properties @@ -0,0 +1,68 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(27500) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=100 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=10000 +user.username=user_${index?string("0000")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u_1hi.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u_1hi.properties new file mode 100644 index 0000000000..84b4dc56de --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u_1hi.properties @@ -0,0 +1,68 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(1) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=100 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=10000 +user.username=user_${index?string("0000")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u_1hi_10000res_100sc_100po_100pe.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u_1hi_10000res_100sc_100po_100pe.properties new file mode 100644 index 0000000000..df88c0012b --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_10000u_1hi_10000res_100sc_100po_100pe.properties @@ -0,0 +1,153 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(1) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=100 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=${(!isPublicClient())?c} +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=10000 +user.username=user_${index?string("0000")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + + + +### AUTHZ +# RESOURCE SERVER +resourceServer.allowRemoteResourceManagement=false +resourceServer.policyEnforcementMode=ENFORCING + +# SCOPE +scopesPerResourceServer=100 +scope.name=scope_${index}_of_${resourceServer.clientId} +scope.displayName=Scope ${index} of ${resourceServer.clientId} + +# RESOURCE +resourcesPerResourceServer=10000 +resource.name=resource_${index?string("0000")}_of_${resourceServer.clientId} +resource.displayName=Resource ${index} of ${resourceServer.clientId} +resource.uri=${resourceServer.client.baseUrl}/resource_${index} +resource.type=urn:${resourceServer.clientId}:resources:default +resource.ownerManagedAccess=${indexBasedRandomBool(50)?c} + +# RESOURCE MAPPINGS +scopesPerResource=1 + + +# ROLE POLICY +rolePoliciesPerResourceServer=100 +rolePolicy.name=role_policy_${index}_of_${resourceServer.clientId} +rolePolicy.description=Role Policy ${index} of ${resourceServer.name} +rolePolicy.logic=POSITIVE + +# ROLE POLICY ROLE DEFINITION +rolePolicyRoleDefinition.required=${indexBasedRandomBool(50)?c} +realmRolesPerRolePolicy=1 +clientRolesPerRolePolicy=3 + + +# JS POLICY +jsPoliciesPerResourceServer=100 +jsPolicy.name=js_policy_${index}_of_${resourceServer.clientId} +jsPolicy.description=JavaScript Policy ${index} of ${resourceServer.name} +jsPolicy.code=// TODO add some JavaScript code\n// for JavaScript Policy ${index}\n// more\n// lines ... +jsPolicy.logic=POSITIVE + +# USER POLICY +userPoliciesPerResourceServer=100 +userPolicy.name=user_policy_${index}_of_${resourceServer.clientId} +userPolicy.description=User Policy ${index} of ${resourceServer.name} +userPolicy.logic=POSITIVE + +# USER POLICY MAPPINGS +usersPerUserPolicy=1 + + +# CLIENT POLICY +clientPoliciesPerResourceServer=100 +clientPolicy.name=client_policy_${index}_of_${resourceServer.clientId} +clientPolicy.description=Client Policy ${index} of ${resourceServer.name} +clientPolicy.logic=POSITIVE + +# CLIENT POLICY MAPPINGS +clientsPerClientPolicy=1 + + +# RESOURCE PERMISSION +resourcePermissionsPerResourceServer=100 +resourcePermission.name=resource_permission_${index}_of_${resourceServer.clientId} +resourcePermission.description=Resource Permisison ${index} of ${resourceServer.name} +resourcePermission.resourceType=<#if indexBasedRandomBool(50)>urn:${resourceServer.clientId}:resources:default<#else> +resourcePermission.decisionStrategy=UNANIMOUS + +# RESOURCE PERMISSION MAPPINGS +resourcesPerResourcePermission=1 +policiesPerResourcePermission=3 + + +# SCOPE PERMISSION +scopePermissionsPerResourceServer=100 +scopePermission.name=scope_permission_${index}_of_${resourceServer.clientId} +scopePermission.description=Scope Permisison ${index} of ${resourceServer.name} +scopePermission.decisionStrategy=UNANIMOUS + +# SCOPE PERMISSION MAPPINGS +scopesPerScopePermission=1 +policiesPerScopePermission=3 diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_100c_500000u_1hi.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_500000u_1hi.properties new file mode 100644 index 0000000000..a3ef337d5e --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_100c_500000u_1hi.properties @@ -0,0 +1,68 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(1) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=100 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=500000 +user.username=user_${index?string("000000")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=0 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=0 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/1r_10c_100u.properties b/testsuite/performance/tests/src/test/resources/dataset/1r_10c_100u.properties new file mode 100644 index 0000000000..e23f386b0f --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/1r_10c_100u.properties @@ -0,0 +1,68 @@ +# REALM +realms=1 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(27500) + +# REALM ROLE +realmRolesPerRealm=10 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=10 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=false +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=10 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=100 +user.username=user_${index?string("00")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=3 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, diff --git a/testsuite/performance/tests/src/test/resources/dataset/default.properties b/testsuite/performance/tests/src/test/resources/dataset/default.properties new file mode 100644 index 0000000000..16c7e4a34d --- /dev/null +++ b/testsuite/performance/tests/src/test/resources/dataset/default.properties @@ -0,0 +1,154 @@ +# REALM +realms=3 +realm.realm=realm_${index} +realm.displayName=Realm ${index} +realm.enabled=true +realm.registrationAllowed=true +realm.accessTokenLifeSpan=60 +realm.passwordPolicy=hashIterations(27500) + +# REALM ROLE +realmRolesPerRealm=3 +realmRole.name=role_${index?string("00")}_of_${realm.realm} +realmRole.description=Role ${index} of ${realm.displayName} + +# CLIENT +clientsPerRealm=3 +client.clientId=client_${index?string("00")}_of_${realm.realm} +client.name=Client ${index} of ${realm.displayName} +client.description=Description of ${name} +client.rootUrl= +client.adminUrl= +client.baseUrl=http://clients.${realm.realm}.test/client_${index} +client.enabled=true +client.secret=secret_of_${clientId} +# TODO support for multiple redirect uris +#client.redirectUris=${baseUrl}/* http://load-balancing-domain.test/${clientId}/* +client.redirectUris=${baseUrl}/* +client.webOrigins= +client.protocol=openid-connect +client.publicClient=<#if index % 3 == 0>true<#else>false +client.bearerOnly=<#if index % 3 == 1>true<#else>false +client.authorizationServicesEnabled=${(!isPublicClient())?c} +client.serviceAccountsEnabled=${authorizationServicesEnabled?c} + +# CLIENT ROLE +clientRolesPerClient=3 +clientRole.name=clientrole_${index?string("00")}_of_${client.clientId} +clientRole.description=Role ${index} of ${client.name} + +# USER +usersPerRealm=3 +user.username=user_${index?string("00")}_of_${realm.realm} +user.enabled=true +user.email=${username}@email.test +user.emailVerified=true +user.firstName=User_${index} +user.lastName=O'Realm_${realm.index} + +credential.type=password +credential.value=password_${index}_of_${user.username} +credential.temporary=false + +# USER ATTRIBUTE +attributesPerUser=3 +userAttribute.name=attribute_${index?string("00")} +userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + +# USER ROLE MAPPINGS +realmRolesPerUser=1 +clientRolesPerUser=3 + + +# GROUP +groupsPerRealm=3 +group.name=group_${index?string("00")}_of ${realm.realm} + +# GROUP ATTRIBUTE +attributesPerGroup=3 +groupAttribute.name=attribute_${index?string("00")} +groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>, + + +### AUTHZ +# RESOURCE SERVER +resourceServer.allowRemoteResourceManagement=false +resourceServer.policyEnforcementMode=ENFORCING + +# SCOPE +scopesPerResourceServer=3 +scope.name=scope_${index}_of_${resourceServer.clientId} +scope.displayName=Scope ${index} of ${resourceServer.clientId} + +# RESOURCE +resourcesPerResourceServer=3 +resource.name=resource_${index}_of_${resourceServer.clientId} +resource.displayName=Resource ${index} of ${resourceServer.clientId} +resource.uri=${resourceServer.client.baseUrl}/resource_${index} +resource.type=urn:${resourceServer.clientId}:resources:default +resource.ownerManagedAccess=${indexBasedRandomBool(50)?c} + +# RESOURCE MAPPINGS +scopesPerResource=1 + + +# ROLE POLICY +rolePoliciesPerResourceServer=3 +rolePolicy.name=role_policy_${index}_of_${resourceServer.clientId} +rolePolicy.description=Role Policy ${index} of ${resourceServer.name} +rolePolicy.logic=POSITIVE + +# ROLE POLICY ROLE DEFINITION +rolePolicyRoleDefinition.required=${indexBasedRandomBool(50)?c} +realmRolesPerRolePolicy=1 +clientRolesPerRolePolicy=3 + + +# JS POLICY +jsPoliciesPerResourceServer=3 +jsPolicy.name=js_policy_${index}_of_${resourceServer.clientId} +jsPolicy.description=JavaScript Policy ${index} of ${resourceServer.name} +jsPolicy.code=// TODO add some JavaScript code\n// for JavaScript Policy ${index}\n// more\n// lines ... +jsPolicy.logic=POSITIVE + +# USER POLICY +userPoliciesPerResourceServer=3 +userPolicy.name=user_policy_${index}_of_${resourceServer.clientId} +userPolicy.description=User Policy ${index} of ${resourceServer.name} +userPolicy.logic=POSITIVE + +# USER POLICY MAPPINGS +usersPerUserPolicy=1 + + +# CLIENT POLICY +clientPoliciesPerResourceServer=3 +clientPolicy.name=client_policy_${index}_of_${resourceServer.clientId} +clientPolicy.description=Client Policy ${index} of ${resourceServer.name} +clientPolicy.logic=POSITIVE + +# CLIENT POLICY MAPPINGS +clientsPerClientPolicy=1 + + +# RESOURCE PERMISSION +resourcePermissionsPerResourceServer=3 +resourcePermission.name=resource_permission_${index}_of_${resourceServer.clientId} +resourcePermission.description=Resource Permisison ${index} of ${resourceServer.name} +resourcePermission.resourceType=<#if indexBasedRandomBool(50)>urn:${resourceServer.clientId}:resources:default<#else> +resourcePermission.decisionStrategy=UNANIMOUS + +# RESOURCE PERMISSION MAPPINGS +resourcesPerResourcePermission=1 +policiesPerResourcePermission=3 + + +# SCOPE PERMISSION +scopePermissionsPerResourceServer=3 +scopePermission.name=scope_permission_${index}_of_${resourceServer.clientId} +scopePermission.description=Scope Permisison ${index} of ${resourceServer.name} +scopePermission.decisionStrategy=UNANIMOUS + +# SCOPE PERMISSION MAPPINGS +scopesPerScopePermission=1 +policiesPerScopePermission=3 diff --git a/testsuite/performance/tests/src/test/resources/logback-test.xml b/testsuite/performance/tests/src/test/resources/logback-test.xml index 153511f2e8..81b16f9878 100644 --- a/testsuite/performance/tests/src/test/resources/logback-test.xml +++ b/testsuite/performance/tests/src/test/resources/logback-test.xml @@ -9,16 +9,28 @@ %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx - false + + + %msg%n%rEx + + + + + + + - + - - - + + + + + + \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleScenarioBuilder.scala b/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleScenarioBuilder.scala index 332aae8963..22c6f7d0d5 100644 --- a/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleScenarioBuilder.scala +++ b/testsuite/performance/tests/src/test/scala/keycloak/AdminConsoleScenarioBuilder.scala @@ -13,6 +13,7 @@ import org.jboss.perf.util.Util import org.jboss.perf.util.Util.randomUUID import org.keycloak.gatling.Utils.{urlEncodedRoot, urlencode} import org.keycloak.performance.TestConfig +import org.keycloak.performance.templates.DatasetTemplate /** @@ -47,12 +48,17 @@ object AdminConsoleScenarioBuilder { lastRefresh + TestConfig.refreshTokenPeriod * 1000 < System.currentTimeMillis()) } + val datasetTemplate = new DatasetTemplate() + datasetTemplate.validateConfiguration + val dataset = datasetTemplate.produce + val realmsIterator = dataset.randomRealmIterator + } class AdminConsoleScenarioBuilder { var chainBuilder = exec(s => { - val realm = TestConfig.randomRealmsIterator().next() + val realm = realmsIterator.next val serverUrl = TestConfig.serverUrisIterator.next() s.setAll( "keycloakServer" -> serverUrl, @@ -61,7 +67,7 @@ class AdminConsoleScenarioBuilder { "state" -> randomUUID(), "nonce" -> randomUUID(), "randomClientId" -> ("client_" + randomUUID()), - "realm" -> realm, + "realm" -> realm.getRepresentation.getRealm, "username" -> TestConfig.authUser, "password" -> TestConfig.authPassword, "clientId" -> "security-admin-console" diff --git a/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala b/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala index 68115578c5..3f9613fc52 100644 --- a/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala +++ b/testsuite/performance/tests/src/test/scala/keycloak/CommonSimulation.scala @@ -2,7 +2,7 @@ package keycloak import io.gatling.core.Predef._ import io.gatling.http.Predef._ -import org.keycloak.performance.log.LogProcessor +import org.keycloak.gatling.log.LogProcessor import io.gatling.core.validation.Validation import io.gatling.core.controller.inject.InjectionStep @@ -21,8 +21,8 @@ abstract class CommonSimulation extends Simulation { println("Using test parameters:\n" + TestConfig.toStringCommonTestParameters); printSpecificTestParameters println() - println("Using dataset properties:\n" + TestConfig.toStringDatasetProperties) - println() +// println("Using dataset properties:\n" + TestConfig.toStringDatasetProperties) +// println() println("Using assertion properties:\n" + TestConfig.toStringAssertionProperties) println() println("Timestamps: \n" + TestConfig.toStringTimestamps) diff --git a/testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala b/testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala index 951b99544c..b1b1af6623 100644 --- a/testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala +++ b/testsuite/performance/tests/src/test/scala/keycloak/OIDCScenarioBuilder.scala @@ -16,6 +16,7 @@ import org.jboss.perf.util.Util.randomUUID import org.keycloak.adapters.spi.HttpFacade.Cookie import org.keycloak.gatling.AuthorizeAction import org.keycloak.performance.TestConfig +import org.keycloak.performance.templates.DatasetTemplate /** @@ -73,6 +74,11 @@ object OIDCScenarioBuilder { .adapterExchangesCodeForTokens() .thinkPause() .randomLogout() + + val datasetTemplate = new DatasetTemplate() + datasetTemplate.validateConfiguration + val dataset = datasetTemplate.produce + val usersIterator = dataset.randomUsersIterator } @@ -81,25 +87,23 @@ class OIDCScenarioBuilder { var chainBuilder = exec(s => { - // initialize session with host, user, client app, login failure ratio ... - val realm = TestConfig.getRealmsIterator().next(); - val userInfo = TestConfig.getUsersIterator(realm).next(); - val clientInfo = TestConfig.getConfidentialClientsIterator(realm).next() + val user = usersIterator.next + val client = user.randomConfidentialClientIterator.next AuthorizeAction.init(s) .setAll("keycloakServer" -> TestConfig.serverUrisIterator.next(), "state" -> randomUUID(), "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, - "secret" -> clientInfo.secret, - "appUrl" -> clientInfo.appUrl + "realm" -> user.getRealm.toString, + "firstName" -> user.getRepresentation.getFirstName, + "lastName" -> user.getRepresentation.getLastName, + "email" -> user.getRepresentation.getEmail, + "username" -> user.getRepresentation.getUsername, + "password" -> user.getCredentials.get(0).getRepresentation.getValue, + "clientId" -> client.getRepresentation.getClientId, + "secret" -> client.getRepresentation.getSecret, + "appUrl" -> client.getRepresentation.getBaseUrl ) }) .exitHereIfFailed diff --git a/testsuite/performance/tests/src/test/scala/org/keycloak/performance/Admin.scala b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/Admin.scala new file mode 100644 index 0000000000..e85222c057 --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/Admin.scala @@ -0,0 +1,114 @@ +package org.keycloak.performance + +import io.gatling.core.Predef._ +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit._ +import java.util.concurrent.TimeoutException +import scala.concurrent.duration._ +import java.util.concurrent.CyclicBarrier +import io.gatling.core.structure.ChainBuilder + +object Admin extends Admin { + + object Sync { // java.util.concurrent.CyclicBarrier doesn't work with Gatling because of the Akka actor model, need this custom solution + + val usersMax = adminUsers + val usersArrived = new AtomicInteger(0) + val latchOpen = new AtomicBoolean(false) + + def isLatchOpen = latchOpen.get + + def waitForPreviousLatchToCloseIfStillOpen = asLongAs ( s => latchOpen.get && !isGlobalStopRequested, "waitForLatchToCloseTimeout") ( + checkGlobalStopRequest + + .doIfOrElse ( s => (s("waitForLatchToCloseTimeout").as[Int] < 100)) ( // 100 * 100 millis = 10 seconds + exec { s => +// println("previous latch still open, waiting for its closure") + s + }.pause(100.millisecond) + ) ( + exec { s => + throw new IllegalStateException("Waiting for previous latch to close timed out. This shoulnd't happen.") + s + }.exitHereIfFailed + ) + ) + + def arriveAtLatchAndOpenIfLast(latchName:String) = exec { s => + usersArrived.incrementAndGet + if (usersArrived.get >= usersMax && !latchOpen.get) { + println("opening latch: "+latchName) + latchOpen.set(true) // last arriving opens the latch + } + s + } + + def waitForLatchToOpen(timeout:Integer) = asLongAs ( s => !latchOpen.get, "waitForLatchToOpenTimeout") ( + checkGlobalStopRequest + .doIfOrElse ( s => (s("waitForLatchToOpenTimeout").as[Int] < timeout)) ( + exec { s => +// println("waiting for latch to open. usersArrived: "+usersArrived.get +"/"+usersMax+ ", timeout: "+(timeout - s("waitForLatchToOpenTimeout").as[Int])) + s + }.pause(1.second) + ) ( + exec { s => + throw new TimeoutException("Waiting for others timed out. Missing users: " + (usersMax - usersArrived.get)) + s + }.exitHereIfFailed + ) + ) + + def leaveLatchAndCloseIfLast(latchName:String) = exec { s => + usersArrived.decrementAndGet + if (usersArrived.get <= 0 && latchOpen.get) { + println("closing latch: "+latchName) + latchOpen.set(false) // last leaving closes the latch + } + s.remove("waitForLatchToOpenTimeout") + s + } + + def waitForOthers(latchName:String, timeout:Integer) = exec ( + waitForPreviousLatchToCloseIfStillOpen, + arriveAtLatchAndOpenIfLast(latchName), + waitForLatchToOpen(timeout), + leaveLatchAndCloseIfLast(latchName) + ) + + def waitForOthers(latchName:String) : ChainBuilder = waitForOthers(latchName, 10) + + def waitForOthers : ChainBuilder = waitForOthers("") + + + + + val globalStopRequested = new AtomicBoolean(false) + def isGlobalStopRequested = { +// println("isGlobalStopRequested: "+globalStopRequested.get) + globalStopRequested.get + } + def checkGlobalStopRequest = exec { s => + if (isGlobalStopRequested) throw new RuntimeException("Global stop requested.") + s + }.exitHereIfFailed + + def requestGlobalStop : ChainBuilder = exec { s => + println("Requesting global stop.") + globalStopRequested.set(true) + s + }.exec(checkGlobalStopRequest) + + } + +} + +trait Admin extends OIDC { + import Admin._ + + val adminUsers = TestConfig.numOfWorkers + + val adminInjectionProfile = atOnceUsers(adminUsers) + +} \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/scala/org/keycloak/performance/AdminCLI.scala b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/AdminCLI.scala new file mode 100644 index 0000000000..adf8de6c4c --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/AdminCLI.scala @@ -0,0 +1,91 @@ +package org.keycloak.performance + +import java.time.ZonedDateTime +import java.util.concurrent.TimeUnit._ +import scala.collection.JavaConversions._ +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.core.session._ +import io.gatling.core.structure.ChainBuilder +import org.keycloak.performance.iteration._ +import org.keycloak.performance.dataset._ +import org.keycloak.performance.dataset.idm._ +import org.keycloak.performance.dataset.idm.authorization._ + +object AdminCLI extends AdminCLI + +trait AdminCLI extends Admin { + + val adminCLIHttpConf = http + .disableFollowRedirect + .acceptHeader("application/json, text/plain, */*") + + object Auth { + + def init = exec(s => { + val serverUrl = TestConfig.serverUrisIterator.next + s.setAll( + "keycloakServer" -> serverUrl, + "username" -> TestConfig.authUser, + "password" -> TestConfig.authPassword, + "clientId" -> "admin-cli" + ) + }).exitHereIfFailed + + def login = exec( + http("Admin Login") + .post("${keycloakServer}/realms/master/protocol/openid-connect/token") + .headers(ACCEPT_ALL) + .formParam("grant_type", "password") + .formParam("username", "${username}") + .formParam("password", "${password}") + .formParam("client_id", "${clientId}") + .check(status.is(200), + jsonPath("$.access_token").saveAs("accessToken"), + jsonPath("$.refresh_token").saveAs("refreshToken"), + jsonPath("$.expires_in").saveAs("expiresIn"), + header("Date").saveAs("tokenTime"))) + .exitHereIfFailed + .exec{s => + s.set("accessTokenRefreshTime", ZonedDateTime.parse(s("tokenTime").as[String], DATE_FMT_RFC1123).toEpochSecond * 1000) + } + + def needTokenRefresh(session: Session): Boolean = { + val lastRefresh = session("accessTokenRefreshTime").as[Long] + + // 5 seconds before expiry is time to refresh + lastRefresh + session("expiresIn").as[String].toInt * 1000 - 5000 < System.currentTimeMillis() || + // or if refreshTokenPeriod is set force refresh even if not necessary + (TestConfig.refreshTokenPeriod > 0 && + lastRefresh + TestConfig.refreshTokenPeriod * 1000 < System.currentTimeMillis()) + } + + def refreshTokenIfExpired = doIf(s => needTokenRefresh(s)) { + exec{s => println("Access Token Expired. Refreshing.") + s} + .exec( + http("Refresh Token") + .post("${keycloakServer}/realms/master/protocol/openid-connect/token") + .headers(ACCEPT_ALL) + .formParam("grant_type", "refresh_token") + .formParam("refresh_token", "${refreshToken}") + .formParam("client_id", "admin-cli") + .check(status.is(200), + jsonPath("$.access_token").saveAs("accessToken"), + jsonPath("$.refresh_token").saveAs("refreshToken"), + jsonPath("$.expires_in").saveAs("expiresIn"), + header("Date").saveAs("tokenTime")) + ).exec{s => + s.set("accessTokenRefreshTime", ZonedDateTime.parse(s("tokenTime").as[String], DATE_FMT_RFC1123).toEpochSecond * 1000) + } + } + + def logout = exec (refreshTokenIfExpired).exec( + http("Admin Logout") + .get("${keycloakServer}/realms/master/protocol/openid-connect/logout") + .check(status.is(200)) + ) + + } + +} \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/scala/org/keycloak/performance/AdminConsole.scala b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/AdminConsole.scala new file mode 100644 index 0000000000..91599c45ca --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/AdminConsole.scala @@ -0,0 +1,12 @@ +package org.keycloak.performance + +object AdminConsole extends AdminConsole { + +} + +trait AdminConsole extends Admin { + import AdminConsole._ + + // TODO Existing tests from keycloak.AdminConsole* will be moved here. + +} diff --git a/testsuite/performance/tests/src/test/scala/org/keycloak/performance/EmulatedOIDC.scala b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/EmulatedOIDC.scala new file mode 100644 index 0000000000..01fa8bf5c7 --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/EmulatedOIDC.scala @@ -0,0 +1,13 @@ +package org.keycloak.performance + +object EmulatedOIDC extends EmulatedOIDC { + + + // TODO Existing OIDC tests from keycloak.OIDC* will be moved here. + +} + +trait EmulatedOIDC extends OIDC { + import EmulatedOIDC._ + +} \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/scala/org/keycloak/performance/Keycloak.scala b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/Keycloak.scala new file mode 100644 index 0000000000..735bc23f79 --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/Keycloak.scala @@ -0,0 +1,17 @@ +package org.keycloak.performance + +import java.time.format.DateTimeFormatter +import org.keycloak.performance.templates.DatasetTemplate + +trait Keycloak { + + val datasetTemplate = new DatasetTemplate() + datasetTemplate.validateConfiguration + val dataset = datasetTemplate.produce + + val DATE_FMT_RFC1123 = DateTimeFormatter.RFC_1123_DATE_TIME + + val ACCEPT_ALL = Map("Accept" -> "*/*") + val AUTHORIZATION = Map("Authorization" -> "Bearer ${accessToken}") + +} \ No newline at end of file diff --git a/testsuite/performance/tests/src/test/scala/org/keycloak/performance/OIDC.scala b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/OIDC.scala new file mode 100644 index 0000000000..47b1ee9280 --- /dev/null +++ b/testsuite/performance/tests/src/test/scala/org/keycloak/performance/OIDC.scala @@ -0,0 +1,10 @@ +package org.keycloak.performance + +object OIDC extends OIDC { + +} + +trait OIDC extends Keycloak { + import OIDC._ + +} \ No newline at end of file