Remove jpa-performance

Closes #44812

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-12-11 00:16:47 +01:00 committed by GitHub
parent 2e66f5c56c
commit ed69f65a9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 0 additions and 416 deletions

View File

@ -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`.

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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

View File

@ -93,12 +93,6 @@
</dependency>
</dependencies>
</profile>
<profile>
<id>jpa-performance</id>
<modules>
<module>jpa-performance</module>
</modules>
</profile>
<profile>
<id>sssd</id>
<modules>