keycloak-uncached
Changes
testsuite/performance/README.datasets.md 166(+136 -30)
testsuite/performance/README.md 55(+29 -26)
testsuite/performance/tests/pom.xml 131(+89 -42)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/Attribute.java 17(+17 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeMap.java 21(+21 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/AttributeRepresentation.java 28(+28 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttribute.java 21(+21 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringAttributeRepresentation.java 8(+8 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttribute.java 21(+21 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/attr/StringListAttributeRepresentation.java 10(+10 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Creatable.java 74(+74 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Dataset.java 153(+153 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetLoader.java 205(+205 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/DatasetRepresentation.java 19(+19 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ClientPolicy.java 64(+64 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/JsPolicy.java 52(+52 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Policy.java 34(+34 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Resource.java 79(+79 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourcePermission.java 72(+72 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServer.java 148(+148 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerList.java 46(+46 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicy.java 64(+64 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinition.java 22(+22 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyRoleDefinitionSet.java 21(+21 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/Scope.java 63(+63 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ScopePermission.java 72(+72 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/UserPolicy.java 64(+64 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Client.java 80(+80 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRole.java 37(+37 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/ClientRoleMappings.java 40(+40 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Credential.java 53(+53 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Group.java 59(+59 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Realm.java 119(+119 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RealmRole.java 37(+37 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMapper.java 23(+23 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappings.java 49(+49 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/RoleMappingsRepresentation.java 12(+12 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/User.java 101(+101 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/NestedEntity.java 66(+66 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Representable.java 57(+57 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/Updatable.java 25(+25 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/FilteredIterator.java 2(+1 -1)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/Flattened2DList.java 34(+34 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/ListOfLists.java 48(+48 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/LoopingIterator.java 2(+1 -1)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomBooleans.java 54(+54 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIntegers.java 96(+96 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomIterator.java 29(+29 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomSublist.java 43(+43 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/UniqueRandomIntegers.java 41(+41 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringAttributeTemplate.java 21(+21 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringListAttributeTemplate.java 30(+30 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/DatasetTemplate.java 96(+96 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityObjectWrapper.java 35(+35 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplate.java 110(+110 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/EntityTemplateModel.java 88(+88 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ClientPolicyTemplate.java 62(+62 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/JsPolicyTemplate.java 47(+47 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/PolicyTemplate.java 23(+23 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourcePermissionTemplate.java 81(+81 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceServerTemplate.java 101(+101 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ResourceTemplate.java 72(+72 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/RolePolicyTemplate.java 130(+130 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopePermissionTemplate.java 79(+79 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ScopeTemplate.java 44(+44 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/UserPolicyTemplate.java 62(+62 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientRoleTemplate.java 50(+50 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientTemplate.java 67(+67 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/GroupTemplate.java 85(+85 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmRoleTemplate.java 50(+50 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/RealmTemplate.java 87(+87 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/UserTemplate.java 184(+184 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplate.java 61(+61 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateModel.java 45(+45 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplateWrapperList.java 35(+35 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/util/CombinedConfigurationNoInterpolation.java 23(+23 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ConfigurationUtil.java 54(+54 -0)
testsuite/performance/tests/src/main/java/org/keycloak/performance/util/ValidateNumber.java 45(+45 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/DatasetTest.java 109(+109 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/EntityTest.java 101(+101 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/RolePolicyTest.java 23(+23 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ScopeTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleMappingsTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientRoleTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/ClientTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/CredentialTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleMappingsTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmRoleTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/RealmTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/UserTest.java 18(+18 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/ListOfListsTest.java 46(+46 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomBooleansTest.java 47(+47 -0)
testsuite/performance/tests/src/test/java/org/keycloak/performance/iteration/RandomIntegersTest.java 76(+76 -0)
Details
testsuite/performance/README.datasets.md 166(+136 -30)
diff --git a/testsuite/performance/README.datasets.md b/testsuite/performance/README.datasets.md
index 2338be1..08d0082 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
+Before generating data it is necessary to provision/start Keycloak server. This can
+be done automatically by running:
-The first dataset is small and is created quickly. Building of each subsequent dataset continues on top
-of the previous dataset.
+```
+cd testsuite/performance
+mvn clean install
+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.
-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.
+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.
-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.
+## Generate Data
+To generate the *default dataset* run:
```
cd testsuite/performance
+mvn verify -P generate-data
+```
-mvn clean install -Dserver.version=4.0.0.Beta1
+To generate a *specific dataset* from within the project run:
+```
+mvn verify -P generate-data -Ddataset=<NAMED_DATASET>
+```
+This will load dataset properties from `tests/src/test/resources/dataset/${dataset}.properties`.
-mvn verify -Pteardown
-mvn verify -Pprovision
-mvn verify -Pgenerate-data -Ddataset=10r100u1c -DnumOfWorkers=10
-mvn verify -Pexport-dump -Ddataset=10r100u1c
+To generate a specific dataset from a *custom properties file* run:
+```
+mvn verify -P generate-data -Ddataset.properties.file=<FULL_PATH_TO_PROPERTIES_FILE>
+```
-mvn verify -Pgenerate-data -Ddataset=20r100u1c -DstartAtRealmIdx=10 -DnumOfWorkers=10
-mvn verify -Pexport-dump -Ddataset=20r100u1c
+To delete a dataset run:
+```
+mvn verify -P generate-data -Ddataset=… -Ddelete=true
+```
+This will delete all realms specified by the dataset.
-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
+## Indexed Model
-mvn verify -Pgenerate-data -Ddataset=500r100u1c -DstartAtRealmIdx=200 -DnumOfWorkers=10
-mvn verify -Pexport-dump -Ddataset=500r100u1c
-```
+The model is hierarchical with the parent-child relationships determined by primary foreign keys of entities.
-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
+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.
-```
testsuite/performance/README.md 55(+29 -26)
diff --git a/testsuite/performance/README.md b/testsuite/performance/README.md
index 0ccc450..095313b 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=<MAX_EXPECTED_REGISTRATIONS>`
##### Example B:
-1. Generate or import dataset with 100 users: `mvn verify -P generate-data -Ddataset=100u2c`. This will create 1 realm and users 0-99.
+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=<MAX_EXPECTED_REGISTRATIONS>`
testsuite/performance/tests/pom.xml 131(+89 -42)
diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml
index 8ba8af0..70281c8 100644
--- a/testsuite/performance/tests/pom.xml
+++ b/testsuite/performance/tests/pom.xml
@@ -34,11 +34,11 @@
<deployment>singlenode</deployment>
<provisioning.properties>${provisioner}/4cpus/${deployment}</provisioning.properties>
- <dataset>2u2c</dataset>
+ <dataset>default</dataset>
<test.properties>oidc-login-logout</test.properties>
<provisioning.properties.file>${project.basedir}/parameters/provisioning/${provisioning.properties}.properties</provisioning.properties.file>
- <dataset.properties.file>${project.basedir}/parameters/datasets/${dataset}.properties</dataset.properties.file>
+ <dataset.properties.file>${project.basedir}/src/test/resources/dataset/${dataset}.properties</dataset.properties.file>
<test.properties.file>${project.basedir}/parameters/test/${test.properties}.properties</test.properties.file>
<provisioned.system.properties.file>${project.build.directory}/provisioned-system.properties</provisioned.system.properties.file>
@@ -56,9 +56,14 @@
<gatling-plugin.version>2.2.1</gatling-plugin.version>
<scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>
<jboss-logging.version>3.3.0.Final</jboss-logging.version>
+
+ <jackson.version>2.9.6</jackson.version>
+ <jackson.databind.version>${jackson.version}</jackson.databind.version>
+ <jackson.annotations.version>${jackson.databind.version}</jackson.annotations.version>
<gatling.simulationClass>keycloak.OIDCLoginAndLogoutSimulation</gatling.simulationClass>
<gatling.skip.run>true</gatling.skip.run>
+ <surefire.skip.run>true</surefire.skip.run>
<trustStoreArg/>
<trustStorePasswordArg/>
@@ -94,11 +99,35 @@
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson.annotations.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${server.version}</version>
</dependency>
<dependency>
+ <groupId>commons-configuration</groupId>
+ <artifactId>commons-configuration</artifactId>
+ <version>1.10</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-validator</groupId>
+ <artifactId>commons-validator</artifactId>
+ <version>1.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
</dependency>
@@ -137,8 +166,12 @@
<build>
<testResources>
<testResource>
- <directory>src/test/resources</directory>
<filtering>true</filtering>
+ <directory>src/test/resources</directory>
+ <excludes>
+ <exclude>**/*.gz</exclude>
+ <exclude>**/*.properties</exclude>
+ </excludes>
</testResource>
</testResources>
<plugins>
@@ -188,7 +221,6 @@
<quiet>true</quiet>
<files>
<file>${provisioning.properties.file}</file>
- <file>${dataset.properties.file}</file>
<file>${test.properties.file}</file>
</files>
</configuration>
@@ -261,6 +293,7 @@
<skip>${gatling.skip.run}</skip>
<disableCompiler>true</disableCompiler>
<runMultipleSimulations>true</runMultipleSimulations>
+ <propagateSystemProperties>true</propagateSystemProperties>
<jvmArgs>
<!--common params-->
<param>-Dproject.build.directory=${project.build.directory}</param>
@@ -268,14 +301,17 @@
<param>-DauthUser=${keycloak.admin.user}</param>
<param>-DauthPassword=${keycloak.admin.password}</param>
<!--dataset params-->
- <param>-DnumOfRealms=${numOfRealms}</param>
+
+ <param>-Ddataset.properties.file=${dataset.properties.file}</param>
+
+ <!-- <param>-DnumOfRealms=${numOfRealms}</param>
<param>-DusersPerRealm=${usersPerRealm}</param>
<param>-DclientsPerRealm=${clientsPerRealm}</param>
<param>-DrealmRoles=${realmRoles}</param>
<param>-DrealmRolesPerUser=${realmRolesPerUser}</param>
<param>-DclientRolesPerUser=${clientRolesPerUser}</param>
<param>-DclientRolesPerClient=${clientRolesPerClient}</param>
- <param>-DhashIterations=${hashIterations}</param>
+ <param>-DhashIterations=${hashIterations}</param>-->
<!--test params-->
<param>-DusersPerSec=${usersPerSec}</param>
<param>-DrampUpPeriod=${rampUpPeriod}</param>
@@ -309,6 +345,17 @@
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
</plugin>
+
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <dataset.properties.file>${dataset.properties.file}</dataset.properties.file>
+ </systemPropertyVariables>
+ <skip>${surefire.skip.run}</skip>
+ </configuration>
+ </plugin>
+
</plugins>
</build>
@@ -458,10 +505,14 @@
<profile>
<id>generate-data</id>
<properties>
- <startAtRealmIdx>0</startAtRealmIdx>
- <ignoreConflicts>false</ignoreConflicts>
- <skipRealmRoles>false</skipRealmRoles>
- <skipClientRoles>false</skipClientRoles>
+ <delete>false</delete>
+ <log.every>5</log.every>
+ <queue.timeout>60</queue.timeout>
+ <shutdown.timeout>60</shutdown.timeout>
+ <template.cache.size>10000</template.cache.size>
+ <randoms.cache.size>10000</randoms.cache.size>
+ <entity.cache.size>100000</entity.cache.size>
+ <max.heap>2g</max.heap>
</properties>
<build>
<plugins>
@@ -470,30 +521,6 @@
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
- <id>generate-data</id>
- <phase>pre-integration-test</phase>
- <goals>
- <goal>exec</goal>
- </goals>
- <configuration>
- <executable>java</executable>
- <workingDirectory>${project.build.directory}</workingDirectory>
- <arguments>
- <argument>-classpath</argument>
- <classpath/>
- <argument>-DnumOfRealms=${numOfRealms}</argument>
- <argument>-DusersPerRealm=${usersPerRealm}</argument>
- <argument>-DclientsPerRealm=${clientsPerRealm}</argument>
- <argument>-DrealmRoles=${realmRoles}</argument>
- <argument>-DrealmRolesPerUser=${realmRolesPerUser}</argument>
- <argument>-DclientRolesPerUser=${clientRolesPerUser}</argument>
- <argument>-DclientRolesPerClient=${clientRolesPerClient}</argument>
- <argument>-DhashIterations=${hashIterations}</argument>
- <argument>org.keycloak.performance.RealmsConfigurationBuilder</argument>
- </arguments>
- </configuration>
- </execution>
- <execution>
<id>load-data</id>
<phase>pre-integration-test</phase>
<goals>
@@ -503,20 +530,33 @@
<executable>java</executable>
<workingDirectory>${project.build.directory}</workingDirectory>
<arguments>
+
<argument>-classpath</argument>
<classpath/>
- <argument>${trustStoreArg}</argument>
- <argument>${trustStorePasswordArg}</argument>
+
+ <argument>-Xms64m</argument>
+ <argument>-Xmx${max.heap}</argument>
+ <argument>-XX:MetaspaceSize=96M</argument>
+ <argument>-XX:MaxMetaspaceSize=256m</argument>
+
<argument>-Dkeycloak.server.uris=${keycloak.frontend.servers}</argument>
<argument>-DauthUser=${keycloak.admin.user}</argument>
<argument>-DauthPassword=${keycloak.admin.password}</argument>
<argument>-DnumOfWorkers=${numOfWorkers}</argument>
- <argument>-DstartAtRealmIdx=${startAtRealmIdx}</argument>
- <argument>-DignoreConflicts=${ignoreConflicts}</argument>
- <argument>-DskipRealmRoles=${skipRealmRoles}</argument>
- <argument>-DskipClientRoles=${skipClientRoles}</argument>
- <argument>org.keycloak.performance.RealmsConfigurationLoader</argument>
- <argument>benchmark-realms.json</argument>
+ <argument>-Ddataset.properties.file=${dataset.properties.file}</argument>
+ <argument>${trustStoreArg}</argument>
+ <argument>${trustStorePasswordArg}</argument>
+
+ <argument>-Ddelete=${delete}</argument>
+ <argument>-Dlog.every=${log.every}</argument>
+ <argument>-Dqueue.timeout=${queue.timeout}</argument>
+ <argument>-Dshutdown.timeout=${shutdown.timeout}</argument>
+ <argument>-Dtemplate.cache.size=${template.cache.size}</argument>
+ <argument>-Drandoms.cache.size=${randoms.cache.size}</argument>
+ <argument>-Dentity.cache.size=${entity.cache.size}</argument>
+
+ <argument>org.keycloak.performance.dataset.DatasetLoader</argument>
+
</arguments>
</configuration>
</execution>
@@ -595,6 +635,13 @@
</profile>
<profile>
+ <id>junit</id>
+ <properties>
+ <surefire.skip.run>false</surefire.skip.run>
+ </properties>
+ </profile>
+
+ <profile>
<id>collect</id>
<build>
<plugins>
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 0000000..c1d59d6
--- /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 <PE> parent entity
+ * @param <REP> representation
+ */
+public abstract class Attribute<PE extends Entity, REP extends AttributeRepresentation> extends NestedEntity<PE, REP> {
+
+ 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 0000000..509d795
--- /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<AT> extends HashMap<String, AT> {
+
+ public AttributeMap(List<Attribute<?, AttributeRepresentation<AT>>> 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 0000000..df4c812
--- /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<V> {
+
+ 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 0000000..ae109c9
--- /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 <PE>
+ */
+public class StringAttribute<PE extends Entity> extends Attribute<PE, StringAttributeRepresentation> {
+
+ 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 0000000..69ceea7
--- /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<String> {
+}
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 0000000..20ba930
--- /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 <PE> owner entity
+ */
+public class StringListAttribute<PE extends Entity> extends Attribute<PE, StringListAttributeRepresentation> {
+
+ 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 0000000..6effadf
--- /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<List<String>> {
+}
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 0000000..6e5bc96
--- /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<REP> extends Updatable<REP> {
+
+ 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<REP>) 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 0000000..32224d7
--- /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<DatasetRepresentation> {
+
+ private List<Realm> realms;
+
+ private List<User> allUsers;
+ private List<Client> 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<Realm> getRealms() {
+ return realms;
+ }
+
+ public void setRealms(List<Realm> realms) {
+ this.realms = realms;
+ }
+
+ public List<User> getAllUsers() {
+ return allUsers;
+ }
+
+ public void setAllUsers(List<User> allUsers) {
+ this.allUsers = allUsers;
+ }
+
+ public Iterator<User> randomUsersIterator() {
+ return new RandomIterator<>(getAllUsers());
+ }
+
+ public List<Client> getAllClients() {
+ return allClients;
+ }
+
+ public void setAllClients(List<Client> allClients) {
+ this.allClients = allClients;
+ }
+
+ public Iterator<Realm> randomRealmIterator() {
+ return new RandomIterator<>(getRealms());
+ }
+
+ public Stream<Realm> realms() {
+ return getRealms().stream();
+ }
+
+ public Stream<RealmRole> realmRoles() {
+ return getRealms().stream().map(Realm::getRealmRoles).flatMap(List::stream);
+ }
+
+ public Stream<Client> clients() {
+ return getRealms().stream().map(Realm::getClients).flatMap(List::stream);
+ }
+
+ public Stream<ClientRole> clientRoles() {
+ return clients().map(Client::getClientRoles).flatMap(List::stream);
+ }
+
+ public Stream<User> users() {
+ return getRealms().stream().map(Realm::getUsers).flatMap(List::stream);
+ }
+
+ public Stream<Credential> credentials() {
+ return users().map(User::getCredentials).flatMap(List::stream);
+ }
+
+ public Stream<RoleMappings<User>> userRealmRoleMappings() {
+ return users().map(User::getRealmRoleMappings);
+ }
+
+ public Stream<ClientRoleMappings<User>> userClientRoleMappings() {
+ return users().map(User::getClientRoleMappingsList).flatMap(List::stream);
+ }
+
+ public Stream<Group> groups() {
+ return getRealms().stream().map(Realm::getGroups).flatMap(List::stream);
+ }
+
+ public Stream<ResourceServer> resourceServers() {
+ return clients().filter(c -> c.getRepresentation().getAuthorizationServicesEnabled())
+ .map(c -> c.getResourceServer());
+ }
+
+ public Stream<Scope> scopes() {
+ return resourceServers().map(rs -> rs.getScopes()).flatMap(List::stream);
+ }
+
+ public Stream<Resource> resources() {
+ return resourceServers().map(rs -> rs.getResources()).flatMap(List::stream);
+ }
+
+ public Stream<RolePolicy> rolePolicies() {
+ return resourceServers().map(rs -> rs.getRolePolicies()).flatMap(List::stream);
+ }
+
+ public Stream<JsPolicy> jsPolicies() {
+ return resourceServers().map(rs -> rs.getJsPolicies()).flatMap(List::stream);
+ }
+
+ public Stream<UserPolicy> userPolicies() {
+ return resourceServers().map(rs -> rs.getUserPolicies()).flatMap(List::stream);
+ }
+
+ public Stream<ClientPolicy> clientPolicies() {
+ return resourceServers().map(rs -> rs.getClientPolicies()).flatMap(List::stream);
+ }
+
+ public Stream<ResourcePermission> resourcePermissions() {
+ return resourceServers().map(rs -> rs.getResourcePermissions()).flatMap(List::stream);
+ }
+
+ public Stream<ScopePermission> 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 0000000..4eaafd7
--- /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<Keycloak> adminClients = new LinkedBlockingQueue<>();
+ private Throwable error = null;
+
+ Map<String, Integer> 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<? extends Updatable> stream) {
+ if (!errorReported()) {
+ Iterator<? extends Updatable> iterator = stream.iterator();
+ ExecutorService threadPool = Executors.newFixedThreadPool(TestConfig.numOfWorkers);
+ BlockingQueue<Updatable> 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 0000000..3fbe671
--- /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 0000000..988fd97
--- /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 <REP> representation type
+ */
+public abstract class Entity<REP> implements Representable<REP> {
+
+ 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/idm/authorization/ClientPolicy.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/authorization/ClientPolicy.java
new file mode 100644
index 0000000..a2673b6
--- /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<ClientPolicyRepresentation> {
+
+ private List<Client> clients;
+
+ public ClientPolicy(ResourceServer resourceServer, int index) {
+ super(resourceServer, index);
+ }
+
+ @Override
+ public ClientPolicyRepresentation newRepresentation() {
+ return new ClientPolicyRepresentation();
+ }
+
+ public List<Client> getClients() {
+ return clients;
+ }
+
+ public void setClients(List<Client> 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 0000000..e2cae17
--- /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<JSPolicyRepresentation> {
+
+ 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 0000000..f0b3433
--- /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<PR extends AbstractPolicyRepresentation>
+ extends NestedEntity<ResourceServer, PR>
+ implements Creatable<PR> {
+
+ 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 0000000..85d891c
--- /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<ResourceServer, ResourceRepresentation>
+ implements Creatable<ResourceRepresentation> {
+
+ private List<Scope> 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<Scope> getScopes() {
+ return scopes;
+ }
+
+ public void setScopes(List<Scope> 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 0000000..5392e73
--- /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<ResourcePermissionRepresentation> {
+
+ private List<Resource> resources;
+ private List<Policy> policies;
+
+ public ResourcePermission(ResourceServer resourceServer, int index) {
+ super(resourceServer, index);
+ }
+
+ @Override
+ public ResourcePermissionRepresentation newRepresentation() {
+ return new ResourcePermissionRepresentation();
+ }
+
+ public List<Resource> getResources() {
+ return resources;
+ }
+
+ public void setResources(List<Resource> resources) {
+ this.resources = resources;
+ }
+
+ public List<Policy> getPolicies() {
+ return policies;
+ }
+
+ public void setPolicies(List<Policy> 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 0000000..503bfc8
--- /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<Client, ResourceServerRepresentation>
+ implements Updatable<ResourceServerRepresentation> {
+
+ private List<Scope> scopes;
+ private List<Resource> resources;
+ private List<RolePolicy> rolePolicies;
+ private List<JsPolicy> jsPolicies;
+ private List<UserPolicy> userPolicies;
+ private List<ClientPolicy> clientPolicies;
+ private List<ResourcePermission> resourcePermissions;
+ private List<ScopePermission> scopePermissions;
+
+ private List<Policy> 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<Resource> getResources() {
+ return resources;
+ }
+
+ public void setResources(List<Resource> resources) {
+ this.resources = resources;
+ }
+
+ public List<Scope> getScopes() {
+ return scopes;
+ }
+
+ public void setScopes(List<Scope> scopes) {
+ this.scopes = scopes;
+ }
+
+ public List<RolePolicy> getRolePolicies() {
+ return rolePolicies;
+ }
+
+ public void setRolePolicies(List<RolePolicy> 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<JsPolicy> getJsPolicies() {
+ return jsPolicies;
+ }
+
+ public void setJsPolicies(List<JsPolicy> jsPolicies) {
+ this.jsPolicies = jsPolicies;
+ }
+
+ public List<UserPolicy> getUserPolicies() {
+ return userPolicies;
+ }
+
+ public void setUserPolicies(List<UserPolicy> userPolicies) {
+ this.userPolicies = userPolicies;
+ }
+
+ public List<ClientPolicy> getClientPolicies() {
+ return clientPolicies;
+ }
+
+ public void setClientPolicies(List<ClientPolicy> clientPolicies) {
+ this.clientPolicies = clientPolicies;
+ }
+
+ public List<ResourcePermission> getResourcePermissions() {
+ return resourcePermissions;
+ }
+
+ public void setResourcePermissions(List<ResourcePermission> resourcePermissions) {
+ this.resourcePermissions = resourcePermissions;
+ }
+
+ public List<Policy> getAllPolicies() {
+ return allPolicies;
+ }
+
+ public void setAllPolicies(List<Policy> allPolicies) {
+ this.allPolicies = allPolicies;
+ }
+
+ public List<ScopePermission> getScopePermissions() {
+ return scopePermissions;
+ }
+
+ public void setScopePermissions(List<ScopePermission> 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 0000000..3cc04f7
--- /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<ResourceServer> {
+
+ List<Client> clients;
+ List<ResourceServer> resourceServers;
+
+ public ResourceServerList(List<Client> 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 0000000..f613338
--- /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<RolePolicyRepresentation> {
+
+ private List<Role> roles;
+
+ public RolePolicy(ResourceServer resourceServer, int index) {
+ super(resourceServer, index);
+ }
+
+ @Override
+ public RolePolicyRepresentation newRepresentation() {
+ return new RolePolicyRepresentation();
+ }
+
+ public List<Role> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List<Role> 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 0000000..03db5d2
--- /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<RolePolicy, RolePolicyRepresentation.RoleDefinition> {
+
+ 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 0000000..8d05ede
--- /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<RolePolicyRepresentation.RoleDefinition> {
+
+ public RolePolicyRoleDefinitionSet(Collection<RolePolicyRoleDefinition> 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 0000000..c8d8a5b
--- /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<ResourceServer, ScopeRepresentation>
+ implements Creatable<ScopeRepresentation> {
+
+ 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 0000000..1f4a16f
--- /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<ScopePermissionRepresentation> {
+
+ private List<Scope> scopes;
+ private List<Policy> 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<Policy> getPolicies() {
+ return policies;
+ }
+
+ public void setPolicies(List<Policy> policies) {
+ this.policies = policies;
+ }
+
+ public List<Scope> getScopes() {
+ return scopes;
+ }
+
+ public void setScopes(List<Scope> 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 0000000..2b160fc
--- /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<UserPolicyRepresentation> {
+
+ private List<User> users;
+
+ public UserPolicy(ResourceServer resourceServer, int index) {
+ super(resourceServer, index);
+ }
+
+ @Override
+ public UserPolicyRepresentation newRepresentation() {
+ return new UserPolicyRepresentation();
+ }
+
+ public List<User> getUsers() {
+ return users;
+ }
+
+ public void setUsers(List<User> 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/dataset/idm/Client.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/idm/Client.java
new file mode 100644
index 0000000..4c010e1
--- /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<Realm, ClientRepresentation>
+ implements Creatable<ClientRepresentation> {
+
+ private List<ClientRole> 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<ClientRole> getClientRoles() {
+ return clientRoles;
+ }
+
+ public void setClientRoles(List<ClientRole> 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 0000000..0476fe9
--- /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<Client> {
+
+ 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 0000000..c5cc278
--- /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<RM extends RoleMapper> extends RoleMappings<RM> {
+
+ 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 0000000..28026c3
--- /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<User, CredentialRepresentation>
+ implements Updatable<CredentialRepresentation> {
+
+ 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 0000000..ab8c3d0
--- /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<GroupRepresentation> implements Creatable<GroupRepresentation> {
+
+ 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 0000000..2dd2fe8
--- /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<Dataset, RealmRepresentation>
+ implements Creatable<RealmRepresentation> {
+
+ private List<Client> clients;
+ private List<RealmRole> realmRoles;
+ private List<User> users;
+ private List<Group> groups;
+
+ private List<ClientRole> clientRoles; // all clients' roles
+ private List<ResourceServer> 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<User> getUsers() {
+ return users;
+ }
+
+ public void setUsers(List<User> users) {
+ this.users = users;
+ }
+
+ public List<Group> getGroups() {
+ return groups;
+ }
+
+ public void setGroups(List<Group> groups) {
+ this.groups = groups;
+ }
+
+ public List<Client> getClients() {
+ return clients;
+ }
+
+ public void setClients(List<Client> clients) {
+ this.clients = clients;
+ }
+
+ public List<RealmRole> getRealmRoles() {
+ return realmRoles;
+ }
+
+ public void setRealmRoles(List<RealmRole> realmRoles) {
+ this.realmRoles = realmRoles;
+ }
+
+ public List<ClientRole> getClientRoles() {
+ return clientRoles;
+ }
+
+ public void setClientRoles(List<ClientRole> clientRoles) {
+ this.clientRoles = clientRoles;
+ }
+
+ public List<ResourceServer> getResourceServers() {
+ return resourceServers;
+ }
+
+ public void setResourceServers(List<ResourceServer> 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 0000000..e03df5a
--- /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<Realm> {
+
+ 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 0000000..6fd30b8
--- /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 <PE>
+ */
+public abstract class Role<PE extends Entity> extends NestedEntity<PE, RoleRepresentation>
+ implements Creatable<RoleRepresentation> {
+
+ 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 0000000..a86e2cd
--- /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<R> extends NestedEntity<Realm, R> {
+
+ 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 0000000..3cf061c
--- /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 <RM> role-mapper parent entity (user or group)
+ */
+public class RoleMappings<RM extends RoleMapper> extends NestedEntity<RM, RoleMappingsRepresentation>
+ implements Updatable<RoleMappingsRepresentation> {
+
+ 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 0000000..6526ba7
--- /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<RoleRepresentation> {
+
+}
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 0000000..3d075d9
--- /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<UserRepresentation> implements Creatable<UserRepresentation> {
+
+ private List<Credential> credentials;
+ private RoleMappings<User> realmRoleMappings;
+ private List<ClientRoleMappings<User>> 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<User> realmRoleMappings) {
+ this.realmRoleMappings = realmRoleMappings;
+ }
+
+ public void setClientRoleMappingsList(List<ClientRoleMappings<User>> clientRoleMappingsList) {
+ this.clientRoleMappingsList = clientRoleMappingsList;
+ }
+
+ public RoleMappings<User> getRealmRoleMappings() {
+ return realmRoleMappings;
+ }
+
+ public List<ClientRoleMappings<User>> getClientRoleMappingsList() {
+ return clientRoleMappingsList;
+ }
+
+ public List<Credential> getCredentials() {
+ return credentials;
+ }
+
+ public void setCredentials(List<Credential> 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<Client> randomClientIterator() {
+ return new RandomIterator<>(getRealm().getClients());
+ }
+
+ public Iterator<Client> 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/NestedEntity.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/dataset/NestedEntity.java
new file mode 100644
index 0000000..a8c3a5a
--- /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 <PE> parent entity
+ */
+public abstract class NestedEntity<PE extends Entity, REP> extends Entity<REP> {
+
+ 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 0000000..e18ecf6
--- /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 <REP> representation
+ */
+public interface Representable<REP> 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<REP> c = (Class<REP>) 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<REP> c = (Class<REP>) 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 0000000..c6d92f8
--- /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<REP> extends Representable<REP> {
+
+ 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/iteration/Flattened2DList.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/Flattened2DList.java
new file mode 100644
index 0000000..f0cdc6a
--- /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 <XT> type of X-list items
+ * @param <YT> type of Y-list items
+ */
+public abstract class Flattened2DList<XT, YT> extends AbstractList<YT> {
+
+ public abstract List<XT> 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<YT> 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 0000000..74a7405
--- /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<E> extends AbstractList<E> implements Loggable {
+
+ private final List<List<E>> listOfLists = new LinkedList<>();
+
+ public ListOfLists(List<List<E>> listOfLists) {
+ this.listOfLists.addAll(listOfLists);
+ }
+
+ public ListOfLists(List<E>... lists) {
+ this(Arrays.asList(lists));
+ }
+
+ @Override
+ public E get(int index) {
+ E e = null;
+ int rIndex = index;
+ for (List<E> 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/iteration/RandomBooleans.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/iteration/RandomBooleans.java
new file mode 100644
index 0000000..c4934dc
--- /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<Boolean> {
+
+ 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<Integer, RandomBooleans> 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 0000000..9c41894
--- /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<Integer> {
+
+ protected final List<Integer> 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<Integer, RandomIntegers> 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 0000000..6072a84
--- /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<T> implements Iterator<T> {
+
+ List<T> list;
+
+ public RandomIterator(List<T> 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 0000000..d4e97cd
--- /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 <T>
+ */
+public class RandomSublist<T> extends AbstractList<T> {
+
+ private final List<T> originalList;
+
+ private final List<Integer> randomIndexesOfOriginalList;
+
+ private final int size;
+
+ public RandomSublist(List<T> 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<T> 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 0000000..6b4255f
--- /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<Integer, Map<Integer, UniqueRandomIntegers>> 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/attr/StringAttributeTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/attr/StringAttributeTemplate.java
new file mode 100644
index 0000000..42f523b
--- /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 <PE> owner entity
+ */
+public abstract class StringAttributeTemplate<PE extends Entity>
+ extends NestedEntityTemplate<PE, StringAttribute<PE>, 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 0000000..47669a0
--- /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 <PE> owner entity
+ */
+public abstract class StringListAttributeTemplate<PE extends Entity>
+ extends NestedEntityTemplate<PE, StringListAttribute<PE>, StringListAttributeRepresentation> {
+
+ public StringListAttributeTemplate(EntityTemplate parentEntityTemplate) {
+ super(parentEntityTemplate);
+ }
+
+ @Override
+ public void processMappings(StringListAttribute<PE> entity) {
+ }
+
+ @Override
+ public StringListAttribute<PE> newEntity(PE parentEntity, int index) {
+ return new StringListAttribute<>(parentEntity, index);
+ }
+
+}
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 0000000..51e5167
--- /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<Dataset, DatasetRepresentation> {
+
+ 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<Realm, User>() {
+ @Override
+ public List<Realm> getXList() {
+ return dataset.getRealms();
+ }
+
+ @Override
+ public List<User> getYList(Realm realm) {
+ return realm.getUsers();
+ }
+
+ @Override
+ public int getYListSize() {
+ return realmTemplate.userTemplate.usersPerRealm;
+ }
+ });
+ dataset.setAllClients(new Flattened2DList<Realm, Client>() {
+ @Override
+ public List<Realm> getXList() {
+ return dataset.getRealms();
+ }
+
+ @Override
+ public List<Client> 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 0000000..e48e61b
--- /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<Object, TemplateModel> 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 0000000..d298af7
--- /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 <E> entity
+ * @param <R> representation
+ */
+public abstract class EntityTemplate<E extends Entity<R>, R> implements Loggable {
+
+ public static final freemarker.template.Configuration FREEMARKER_CONFIG;
+
+ public static final TypeReference MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {
+ };
+ 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<String, Template> 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<String> 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<String, Object> 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 0000000..a169037
--- /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/idm/authorization/ClientPolicyTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/authorization/ClientPolicyTemplate.java
new file mode 100644
index 0000000..9da91e6
--- /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<ClientPolicy, ClientPolicyRepresentation> {
+
+ 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 0000000..71def1b
--- /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<JsPolicy, JSPolicyRepresentation> {
+
+ 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 0000000..a75654e
--- /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<NIE extends Policy<R>, R extends AbstractPolicyRepresentation>
+ extends NestedEntityTemplate<ResourceServer, NIE, R> {
+
+ 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 0000000..4d021e7
--- /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<ResourcePermission, ResourcePermissionRepresentation> {
+
+ 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 0000000..07ac7d3
--- /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<Client, ResourceServer, ResourceServerRepresentation> {
+
+ 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 0000000..0af6d00
--- /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<ResourceServer, Resource, ResourceRepresentation> {
+
+ 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<User> 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 0000000..2f78f90
--- /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<RolePolicy, RolePolicyRepresentation> {
+
+ 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<RolePolicy, RolePolicyRoleDefinition, RolePolicyRepresentation.RoleDefinition> {
+
+ 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<Entity>) 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 0000000..3c267c7
--- /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<ScopePermission, ScopePermissionRepresentation> {
+
+ 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 0000000..7b76acf
--- /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<ResourceServer, Scope, ScopeRepresentation> {
+
+ 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 0000000..42200de
--- /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<UserPolicy, UserPolicyRepresentation> {
+
+ 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/templates/idm/ClientRoleTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/idm/ClientRoleTemplate.java
new file mode 100644
index 0000000..70b9fef
--- /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<Client, ClientRole, RoleRepresentation> {
+
+ 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 0000000..0aae179
--- /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<Realm, Client, ClientRepresentation> {
+
+ 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 0000000..46b7ee2
--- /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<Realm, Group, GroupRepresentation> {
+
+ 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<Group> {
+
+ 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 0000000..1043759
--- /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<Realm, RealmRole, RoleRepresentation> {
+
+ 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 0000000..1504770
--- /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<Dataset, Realm, RealmRepresentation> {
+
+ 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<Client, ClientRole>() {
+ @Override
+ public List<Client> getXList() {
+ return realm.getClients();
+ }
+
+ @Override
+ public List<ClientRole> 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 0000000..89fef03
--- /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<Realm, User, UserRepresentation> {
+
+ 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<RealmRole> 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<ClientRole> clientRoles = new RandomSublist(
+ user.getRealm().getClientRoles(), // original list
+ user.hashCode(), // random seed
+ clientRolesPerUser, // sublist size
+ false // unique randoms?
+ );
+
+ List<ClientRoleMappings<User>> clientRoleMappingsList = new LinkedList<>();
+ List<Client> clients = clientRoles.stream().map(ClientRole::getClient).distinct().collect(toList());
+ clients.forEach(client -> {
+ List<ClientRole> clientClientRoles = clientRoles.stream().filter(clientRole
+ -> client.equals(clientRole.getClient()))
+ .collect(toList());
+
+ RoleMappingsRepresentation cmr = new RoleMappingsRepresentation();
+ clientClientRoles.forEach(cr -> cmr.add(cr.getRepresentation()));
+
+ ClientRoleMappings<User> 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<User, Credential, CredentialRepresentation> {
+
+ 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<User> {
+
+ 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/NestedEntityTemplate.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/templates/NestedEntityTemplate.java
new file mode 100644
index 0000000..a3310b4
--- /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 <PE>
+ * @param <NE>
+ * @param <R>
+ */
+public abstract class NestedEntityTemplate<PE extends Entity, NE extends NestedEntity<PE, R>, R>
+ extends EntityTemplate<NE, R> {
+
+ private final EntityTemplate parentEntityTemplate;
+
+ public static final int ENTITY_CACHE_SIZE = Integer.parseInt(System.getProperty("entity.cache.size", "100000"));
+
+ private final Map<Integer, NE> 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 0000000..0ddf1e4
--- /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<Object, TemplateModel> 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 0000000..03bb6dc
--- /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 <PE> parent entity type
+ * @param <NIE> child entity type
+ */
+public class NestedEntityTemplateWrapperList<PE extends Entity, NIE extends NestedEntity<PE, R>, R> extends AbstractList<NIE> {
+
+ PE parentEntity;
+ NestedEntityTemplate<PE, NIE, R> nestedEntityTemplate;
+
+ public NestedEntityTemplateWrapperList(PE parentEntity, NestedEntityTemplate<PE, NIE, R> 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/TestConfig.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/TestConfig.java
index 94824d7..174e3be 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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ * @author <a href="mailto:tkyjovsk@redhat.com">Tomas Kyjovsky</a>
*/
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<String> 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<String> 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<UserInfo> sequentialUsersIterator(final String realm) {
return new Iterator<UserInfo>() {
@@ -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<String>() {
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/util/CombinedConfigurationNoInterpolation.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/util/CombinedConfigurationNoInterpolation.java
new file mode 100644
index 0000000..d325339
--- /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 0000000..b595583
--- /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<String> 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 0000000..07a85fc
--- /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 0000000..fea7094
--- /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<String> parseStringList(String string) {
+ return parseStringList(string, ",");
+ }
+
+ public static List<String> parseStringList(String string, String delimiter) {
+ List<String> 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 0000000..8483156
--- /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 153511f..bf8fd92 100644
--- a/testsuite/performance/tests/src/main/resources/logback.xml
+++ b/testsuite/performance/tests/src/main/resources/logback.xml
@@ -12,13 +12,26 @@
<immediateFlush>false</immediateFlush>
</appender>
+ <appender name="CONSOLE_MSG_ONLY" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{HH:mm:ss} %msg%n%rEx</pattern>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="CONSOLE" />
+ </root>
+
<!-- Uncomment for logging ALL HTTP request and responses -->
<!-- <logger name="io.gatling.http" level="TRACE" /> -->
<!-- Uncomment for logging ONLY FAILED HTTP request and responses -->
- <!-- <logger name="io.gatling.http" level="DEBUG" /> -->
+ <logger name="io.gatling" level="WARN" />
- <root level="WARN">
- <appender-ref ref="CONSOLE" />
- </root>
+ <logger name="org.keycloak.performance" level="INFO" additivity="false">
+ <appender-ref ref="CONSOLE_MSG_ONLY" />
+ </logger>
+
+
+ <logger name="ch.qos.logback" level="WARN"/>
</configuration>
\ 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 0000000..a1d163e
--- /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 0000000..a356845
--- /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<Dataset> 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<Map<String, Object>>() {
+ };
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setSerializationInclusion(Include.NON_NULL);
+ Map<String, Object> 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<Dataset> 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 0000000..2f63597
--- /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<T extends Entity> 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<T> entityStream(Dataset dataset);
+
+ public Stream<T> d1EntityStream() {
+ return entityStream(getD1());
+ }
+
+ public Stream<T> d2EntityStream() {
+ return entityStream(getD2());
+ }
+
+ @Test
+ public void testHashCode() {
+ logger().info(this.getClass().getSimpleName() + " testHashCode()");
+ Iterator<T> e1i = d1EntityStream().iterator();
+ Iterator<T> 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<T> e1i = d1EntityStream().iterator();
+ Iterator<T> 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/authorization/ResourceServerTest.java b/testsuite/performance/tests/src/test/java/org/keycloak/performance/dataset/idm/authorization/ResourceServerTest.java
new file mode 100644
index 0000000..b50bebc
--- /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<ResourceServer> {
+
+ @Override
+ public Stream<ResourceServer> 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 0000000..a22e8c1
--- /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<Resource> {
+
+ @Override
+ public Stream<Resource> 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 0000000..ba6220b
--- /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<RolePolicy> {
+
+ @Test
+ public void testRoles() {
+ }
+
+ @Override
+ public Stream<RolePolicy> 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 0000000..837f5a0
--- /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<Scope> {
+
+ @Override
+ public Stream<Scope> entityStream(Dataset dataset) {
+ return dataset.scopes();
+ }
+
+}
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 0000000..fb56de9
--- /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<ClientRoleMappings<User>> {
+
+ @Override
+ public Stream<ClientRoleMappings<User>> 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 0000000..8311969
--- /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<ClientRole> {
+
+ @Override
+ public Stream<ClientRole> 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 0000000..31f2211
--- /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<Client>{
+
+ @Override
+ public Stream<Client> 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 0000000..860aba5
--- /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<Credential> {
+
+ @Override
+ public Stream<Credential> 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 0000000..bf3ae83
--- /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<RoleMappings<User>> {
+
+ @Override
+ public Stream<RoleMappings<User>> 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 0000000..fad8c1f
--- /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<RealmRole> {
+
+ @Override
+ public Stream<RealmRole> 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 0000000..674cbec
--- /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<Realm> {
+
+ @Override
+ public Stream<Realm> 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 0000000..614b811
--- /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<User> {
+
+ @Override
+ public Stream<User> entityStream(Dataset dataset) {
+ return dataset.users();
+ }
+
+}
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 0000000..b34791b
--- /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<List<String>> lists;
+ List<String> 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<String> 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<String> 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 0000000..73e7747
--- /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<Boolean> list1 = new RandomBooleans(seed1, 50).stream().limit(10).collect(Collectors.toList());
+ List<Boolean> list2 = new RandomBooleans(seed1, 50).stream().limit(10).collect(Collectors.toList());
+ List<Boolean> 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 0000000..f1991a8
--- /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<Integer> l1 = new RandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList());
+ List<Integer> l2 = new RandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList());
+ List<Integer> 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<Integer> l1 = new UniqueRandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList());
+ List<Integer> l2 = new UniqueRandomIntegers(seed1, 20).stream().limit(10).collect(Collectors.toList());
+ List<Integer> 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/dataset/100r_100c_100u.properties b/testsuite/performance/tests/src/test/resources/dataset/100r_100c_100u.properties
new file mode 100644
index 0000000..7ed6c77
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..6bb9484
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..69b9088
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..71bbe0d
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..84b4dc5
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..df88c00
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
+
+
+
+### 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></#if>
+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 0000000..a3ef337
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..e23f386
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
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 0000000..16c7e4a
--- /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</#if>
+client.bearerOnly=<#if index % 3 == 1>true<#else>false</#if>
+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>,</#sep></#list>
+
+# 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>,</#sep></#list>
+
+
+### 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></#if>
+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 153511f..81b16f9 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 @@
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx</pattern>
</encoder>
- <immediateFlush>false</immediateFlush>
</appender>
+ <appender name="CONSOLE_MSG_ONLY" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%msg%n%rEx</pattern>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="CONSOLE" />
+ </root>
+
<!-- Uncomment for logging ALL HTTP request and responses -->
<!-- <logger name="io.gatling.http" level="TRACE" /> -->
<!-- Uncomment for logging ONLY FAILED HTTP request and responses -->
- <!-- <logger name="io.gatling.http" level="DEBUG" /> -->
+ <logger name="io.gatling" level="WARN" />
- <root level="WARN">
- <appender-ref ref="CONSOLE" />
- </root>
+ <logger name="org.keycloak.performance" level="INFO" additivity="false">
+ <appender-ref ref="CONSOLE_MSG_ONLY" />
+ </logger>
+
+ <logger name="ch.qos.logback" level="WARN"/>
+
</configuration>
\ 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 332aae8..22c6f7d 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 6811557..3f9613f 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 951b995..b1b1af6 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 0000000..e85222c
--- /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 0000000..adf8de6
--- /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 0000000..91599c4
--- /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 0000000..01fa8bf
--- /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 0000000..735bc23
--- /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 0000000..47b1ee9
--- /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