mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
Remove jpa-performance
Closes #44812 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
2e66f5c56c
commit
ed69f65a9c
@ -1,63 +0,0 @@
|
||||
# Keycloak JPA Performance Tests
|
||||
|
||||
## Test Phases
|
||||
|
||||
1. Create individual users
|
||||
2. Delete realm **Optional**
|
||||
3. Re-import realm **Optional**
|
||||
4. Delete individual users
|
||||
|
||||
Phases 2 and 3 are activated by property `many.users.reimport=true|false`.
|
||||
|
||||
|
||||
## How to run
|
||||
|
||||
1. Build the Arquilian Base Testsuite module: `/testsuite/integration-arquillian/base`
|
||||
2. Run the test from this module using `mvn test` or `mvn clean test`.
|
||||
|
||||
Optional parameters:
|
||||
* `many.users.count` - Number of users to add/delete. Default: *10000*.
|
||||
* `many.users.batch` - Measurement batch size. Default: *1000*.
|
||||
* `many.users.reimport` - Switch for phases 2 and 3. Default: *false*.
|
||||
* `many.users.minTokenValidity` - Minimum validity of admin-client's access token. Default: *10000*. (ms)
|
||||
* `many.users.read.after.create` - If true, then additional request to read user is send always after the user is created. Default: *false*
|
||||
* `many.users.create.objects` - If true, then some additional objects will be added to each user (2 attributes, password credential, 2 group mappings, 1 required action) Default: *false*
|
||||
* `many.users.create.social.links` - If true, then one social (identityProvider) link will be added to each user and then later read. Default: *false*
|
||||
* `keycloak.connectionsJpa.globalStatsInterval` - Interval in seconds to log Hibernate statistics into log. Default: *-1* (which means statistics are disabled)
|
||||
|
||||
|
||||
### With MySQL
|
||||
|
||||
Start dockerized MySQL:
|
||||
```
|
||||
docker run --name mysql-keycloak -e MYSQL_ROOT_PASSWORD=keycloak -e MYSQL_DATABASE=keycloak -e MYSQL_USER=keycloak -e MYSQL_PASSWORD=keycloak -d -p 3306:3306 mysql
|
||||
```
|
||||
|
||||
Additional test parameters:
|
||||
```
|
||||
-Pclean-jpa
|
||||
-Dkeycloak.connectionsJpa.url=jdbc:mysql://localhost/keycloak
|
||||
-Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver
|
||||
-Dkeycloak.connectionsJpa.user=keycloak
|
||||
-Dkeycloak.connectionsJpa.password=keycloak
|
||||
```
|
||||
|
||||
### With PostgreSQL
|
||||
|
||||
Start dockerized PostgreSQL:
|
||||
```
|
||||
docker run --name postgres-keycloak -e POSTGRES_PASSWORD=keycloak -d -p 5432:5432 postgres
|
||||
```
|
||||
|
||||
Additional test parameters:
|
||||
```
|
||||
-Pclean-jpa
|
||||
-Dkeycloak.connectionsJpa.url=jdbc:postgresql://localhost/postgres
|
||||
-Dkeycloak.connectionsJpa.driver=org.postgresql.Driver
|
||||
-Dkeycloak.connectionsJpa.user=postgres
|
||||
-Dkeycloak.connectionsJpa.password=keycloak
|
||||
```
|
||||
|
||||
## Reports
|
||||
|
||||
Test creates reports in `target/stats`.
|
||||
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-tests-other</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>integration-arquillian-tests-jpa-performance</artifactId>
|
||||
|
||||
<name>Keycloak JPA Performance Tests</name>
|
||||
|
||||
</project>
|
||||
@ -1,253 +0,0 @@
|
||||
package org.keycloak.testsuite.user;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.Timer;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class ManyUsersTest extends AbstractUserTest {
|
||||
|
||||
private static final int COUNT = Integer.parseInt(System.getProperty("many.users.count", "10000"));
|
||||
private static final int BATCH = Integer.parseInt(System.getProperty("many.users.batch", "1000"));
|
||||
|
||||
// When true, then it will always send another request to GET user after he is created (this trigger some DB queries and cache user on Keycloak side)
|
||||
private static final boolean READ_USER_AFTER_CREATE = Boolean.parseBoolean(System.getProperty("many.users.read.after.create", "false"));
|
||||
|
||||
// When true, then each user will be updated with password, 2 additional attributes, 2 default groups and some required action
|
||||
private static final boolean CREATE_OBJECTS = Boolean.parseBoolean(System.getProperty("many.users.create.objects", "false"));
|
||||
|
||||
// When true, then each user will be updated with 2 federated identity links
|
||||
private static final boolean CREATE_SOCIAL_LINKS = Boolean.parseBoolean(System.getProperty("many.users.create.social.links", "false"));
|
||||
|
||||
private static final boolean REIMPORT = Boolean.parseBoolean(System.getProperty("many.users.reimport", "false"));
|
||||
|
||||
private static final String REALM = "realm_with_many_users";
|
||||
|
||||
private List<UserRepresentation> users;
|
||||
|
||||
private final Timer realmTimer = new Timer();
|
||||
private final Timer usersTimer = new Timer();
|
||||
|
||||
private static final long MIN_TOKEN_VALIDITY = Long.parseLong(System.getProperty("many.users.minTokenValidity", "10000"));
|
||||
long tokenExpirationTime = 0;
|
||||
|
||||
protected boolean tokenMinValidityExpired() {
|
||||
return System.currentTimeMillis() >= tokenExpirationTime - MIN_TOKEN_VALIDITY;
|
||||
}
|
||||
|
||||
protected void refreshToken() {
|
||||
long requestTime = System.currentTimeMillis();
|
||||
adminClient.tokenManager().refreshToken();
|
||||
tokenExpirationTime = requestTime + adminClient.tokenManager().getAccessToken().getExpiresIn() * 1000;
|
||||
}
|
||||
|
||||
protected void refreshTokenIfMinValidityExpired() {
|
||||
if (tokenMinValidityExpired()) {
|
||||
log.info(String.format("Minimum access token validity (%s ms) expired --> refreshing", MIN_TOKEN_VALIDITY));
|
||||
refreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
protected RealmResource realmResource() {
|
||||
return realmsResouce().realm(REALM);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
log.infof("Reading users after create is %s", READ_USER_AFTER_CREATE ? "ENABLED" : "DISABLED");
|
||||
|
||||
users = new LinkedList<>();
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
users.add(createUserRep("user" + i));
|
||||
}
|
||||
|
||||
realmTimer.reset("create realm before test");
|
||||
createRealm(REALM);
|
||||
|
||||
if (CREATE_OBJECTS) {
|
||||
|
||||
// Assuming default groups and required action already created
|
||||
if (realmResource().getDefaultGroups().isEmpty()) {
|
||||
log.infof("Creating default groups 'group1' and 'group2'.");
|
||||
setDefaultGroup("group1");
|
||||
setDefaultGroup("group2");
|
||||
|
||||
RequiredActionProviderRepresentation updatePassword = realmResource().flows().getRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
|
||||
updatePassword.setDefaultAction(true);
|
||||
realmResource().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePassword);
|
||||
}
|
||||
}
|
||||
|
||||
refreshToken();
|
||||
}
|
||||
|
||||
private void setDefaultGroup(String groupName) {
|
||||
GroupRepresentation group = new GroupRepresentation();
|
||||
group.setName(groupName);
|
||||
Response resp = realmResource().groups().add(group);
|
||||
String groupId = ApiUtil.getCreatedId(resp);
|
||||
resp.close();
|
||||
realmResource().addDefaultGroup(groupId);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
realmTimer.clearStats(true, true, false);
|
||||
usersTimer.clearStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserRepresentation createUser(UsersResource users, UserRepresentation user) {
|
||||
// Add some additional attributes to user
|
||||
if (CREATE_OBJECTS) {
|
||||
Map<String, List<String>> attrs = new HashMap<>();
|
||||
attrs.put("attr1", Collections.singletonList("val1"));
|
||||
attrs.put("attr2", Collections.singletonList("val2"));
|
||||
user.setAttributes(attrs);
|
||||
}
|
||||
|
||||
UserRepresentation userRep = super.createUser(users, user);
|
||||
|
||||
// Add password
|
||||
if (CREATE_OBJECTS) {
|
||||
CredentialRepresentation password = new CredentialRepresentation();
|
||||
password.setType(CredentialRepresentation.PASSWORD);
|
||||
password.setValue("password");
|
||||
password.setTemporary(false);
|
||||
users.get(userRep.getId()).resetPassword(password);
|
||||
}
|
||||
|
||||
// Add social link
|
||||
if (CREATE_SOCIAL_LINKS) {
|
||||
createSocialLink("facebook", users, userRep.getId());
|
||||
}
|
||||
|
||||
return userRep;
|
||||
}
|
||||
|
||||
private void createSocialLink(String provider, UsersResource users, String userId) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
|
||||
FederatedIdentityRepresentation link = new FederatedIdentityRepresentation();
|
||||
link.setIdentityProvider(provider);
|
||||
link.setUserId(uuid);
|
||||
link.setUserName(uuid);
|
||||
users.get(userId).addFederatedIdentity(provider, link);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void manyUsers() throws IOException {
|
||||
RealmRepresentation realm = realmResource().toRepresentation();
|
||||
realm.setUsers(users);
|
||||
|
||||
// CREATE
|
||||
realmTimer.reset("create " + users.size() + " users");
|
||||
usersTimer.reset("create " + BATCH + " users");
|
||||
int i = 0;
|
||||
for (UserRepresentation user : users) {
|
||||
refreshTokenIfMinValidityExpired();
|
||||
UserRepresentation createdUser = createUser(realmResource().users(), user);
|
||||
|
||||
// Send additional request to read every user after he is created
|
||||
if (READ_USER_AFTER_CREATE) {
|
||||
UserRepresentation returned = realmResource().users().get(createdUser.getId()).toRepresentation();
|
||||
Assert.assertEquals(returned.getId(), createdUser.getId());
|
||||
}
|
||||
|
||||
// Send additional request to read social links of user
|
||||
if (CREATE_SOCIAL_LINKS) {
|
||||
List<FederatedIdentityRepresentation> fedIdentities = realmResource().users().get(createdUser.getId()).getFederatedIdentity();
|
||||
}
|
||||
|
||||
if (++i % BATCH == 0) {
|
||||
usersTimer.reset();
|
||||
log.info("Created users: " + i + " / " + users.size());
|
||||
}
|
||||
}
|
||||
if (i % BATCH != 0) {
|
||||
usersTimer.reset();
|
||||
log.info("Created users: " + i + " / " + users.size());
|
||||
}
|
||||
|
||||
if (REIMPORT) {
|
||||
|
||||
// SAVE REALM
|
||||
realmTimer.reset("save realm with " + users.size() + " users");
|
||||
File realmFile = new File(PROJECT_BUILD_DIRECTORY, REALM + ".json");
|
||||
JsonSerialization.writeValueToStream(new BufferedOutputStream(new FileOutputStream(realmFile)), realm);
|
||||
|
||||
// DELETE REALM
|
||||
realmTimer.reset("delete realm with " + users.size() + " users");
|
||||
realmResource().remove();
|
||||
try {
|
||||
realmResource().toRepresentation();
|
||||
fail("realm not deleted");
|
||||
} catch (Exception ex) {
|
||||
log.debug("realm deleted");
|
||||
}
|
||||
|
||||
// RE-IMPORT SAVED REALM
|
||||
realmTimer.reset("re-import realm with " + realm.getUsers().size() + " users");
|
||||
realmsResouce().create(realm);
|
||||
realmTimer.reset("load " + realm.getUsers().size() + " users");
|
||||
users = realmResource().users().search("", 0, -1);
|
||||
|
||||
}
|
||||
|
||||
// DELETE INDIVIDUAL USERS
|
||||
realmTimer.reset("delete " + users.size() + " users");
|
||||
usersTimer.reset("delete " + BATCH + " users", false);
|
||||
i = 0;
|
||||
for (UserRepresentation user : users) {
|
||||
refreshTokenIfMinValidityExpired();
|
||||
realmResource().users().get(user.getId()).remove();
|
||||
if (++i % BATCH == 0) {
|
||||
usersTimer.reset();
|
||||
log.info("Deleted users: " + i + " / " + users.size());
|
||||
}
|
||||
}
|
||||
if (i % BATCH != 0) {
|
||||
usersTimer.reset();
|
||||
log.info("Deleted users: " + i + " / " + users.size());
|
||||
}
|
||||
realmTimer.reset();
|
||||
}
|
||||
|
||||
private void createRealm(String REALM) {
|
||||
RealmRepresentation rep = new RealmRepresentation();
|
||||
rep.setRealm(REALM);
|
||||
adminClient.realms().create(rep);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
#
|
||||
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
log4j.rootLogger=info
|
||||
|
||||
log4j.appender.keycloak=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.keycloak.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.keycloak.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
|
||||
|
||||
log4j.appender.testsuite=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.testsuite.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.testsuite.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %m%n
|
||||
|
||||
log4j.logger.org.keycloak=off, keycloak
|
||||
|
||||
log4j.logger.org.keycloak.testsuite=debug, testsuite
|
||||
log4j.additivity.org.keycloak.testsuite=false
|
||||
|
||||
# Enable to view events
|
||||
# log4j.logger.org.keycloak.events=debug
|
||||
|
||||
# Enable to view loaded SPI and Providers
|
||||
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
|
||||
# log4j.logger.org.keycloak.provider.ProviderManager=debug
|
||||
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
|
||||
|
||||
# Liquibase updates logged with "info" by default. Logging level can be changed by system property "keycloak.liquibase.logging.level"
|
||||
keycloak.liquibase.logging.level=info
|
||||
log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase.logging.level}
|
||||
log4j.logger.org.keycloak.connections.jpa=debug
|
||||
|
||||
# Enable to view database updates
|
||||
# log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug
|
||||
# log4j.logger.org.keycloak.migration.MigrationModelManager=debug
|
||||
|
||||
# Enable to view kerberos/spnego logging
|
||||
# log4j.logger.org.keycloak.broker.kerberos=trace
|
||||
|
||||
# Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server
|
||||
# log4j.logger.org.apache.directory.server.kerberos=debug
|
||||
|
||||
log4j.logger.org.xnio=off
|
||||
log4j.logger.org.hibernate=off
|
||||
log4j.logger.org.jboss.resteasy=warn
|
||||
log4j.logger.org.apache.directory.api=warn
|
||||
log4j.logger.org.apache.directory.server.core=warn
|
||||
@ -93,12 +93,6 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jpa-performance</id>
|
||||
<modules>
|
||||
<module>jpa-performance</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>sssd</id>
|
||||
<modules>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user