keycloak-aplcache
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