mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-21 14:30:05 -06:00
Move ConcurrencyTest.java, AbstractConcurrencyTest.java to the new testsuite
Part of: #34494 Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>
This commit is contained in:
committed by
Stian Thorgersen
parent
adae1bbcb1
commit
cabd7cd474
@@ -13,7 +13,7 @@ class MariaDBTestDatabase extends AbstractContainerTestDatabase {
|
||||
|
||||
@Override
|
||||
public JdbcDatabaseContainer<?> createContainer() {
|
||||
return new MariaDBContainer<>(DockerImageName.parse(DatabaseProperties.getContainerImageName(NAME)).asCompatibleSubstituteFor(NAME));
|
||||
return new MariaDBContainer<>(DockerImageName.parse(DatabaseProperties.getContainerImageName(NAME)).asCompatibleSubstituteFor(NAME)).withCommand("--character-set-server=utf8 --collation-server=utf8_unicode_ci");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,6 +38,11 @@ class MSSQLServerTestDatabase extends AbstractContainerTestDatabase {
|
||||
return "vEry$tron9Pwd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcUrl(boolean internal) {
|
||||
return super.getJdbcUrl(internal) + ";integratedSecurity=false;encrypt=false;trustServerCertificate=true;sendStringParametersAsUnicode=false;";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPostStartCommand() {
|
||||
return List.of("/opt/mssql-tools18/bin/sqlcmd", "-U", "sa", "-P", getPassword(), "-No", "-Q", "CREATE DATABASE " + getDatabase());
|
||||
|
||||
@@ -21,6 +21,11 @@ class MySQLTestDatabase extends AbstractContainerTestDatabase {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcUrl(boolean internal) {
|
||||
return super.getJdbcUrl(internal) + "?allowPublicKeyRetrieval=true";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return LOGGER;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.tests.admin.concurrency;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.testframework.admin.AdminClientFactory;
|
||||
import org.keycloak.testframework.annotations.InjectAdminClientFactory;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.config.Config;
|
||||
import org.keycloak.testframework.realm.ManagedRealm;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class AbstractConcurrencyTest {
|
||||
|
||||
@InjectRealm
|
||||
ManagedRealm managedRealm;
|
||||
|
||||
@InjectAdminClientFactory
|
||||
static AdminClientFactory clientFactory;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractConcurrencyTest.class);
|
||||
|
||||
private static final int DEFAULT_THREADS = 4;
|
||||
|
||||
public static final String REALM_NAME = "default";
|
||||
public static final String MASTER_REALM_NAME = "master";
|
||||
|
||||
// If enabled only one request is allowed at the time. Useful for checking that test is working.
|
||||
private static final boolean SYNCHRONIZED = false;
|
||||
|
||||
protected void run(final KeycloakRunnable... runnables) {
|
||||
run(DEFAULT_THREADS, runnables);
|
||||
}
|
||||
|
||||
public static void run(final int numThreads, final KeycloakRunnable... runnables) {
|
||||
final ExecutorService service = SYNCHRONIZED
|
||||
? Executors.newSingleThreadExecutor()
|
||||
: Executors.newFixedThreadPool(numThreads);
|
||||
|
||||
ThreadLocal<Keycloak> keycloaks = new ThreadLocal<Keycloak>() {
|
||||
@Override
|
||||
protected Keycloak initialValue() {
|
||||
return clientFactory.create().realm(MASTER_REALM_NAME)
|
||||
.clientId(Config.getAdminClientId())
|
||||
.clientSecret(Config.getAdminClientSecret())
|
||||
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
|
||||
.build();
|
||||
}
|
||||
};
|
||||
|
||||
AtomicInteger currentThreadIndex = new AtomicInteger();
|
||||
Collection<Callable<Void>> tasks = new LinkedList<>();
|
||||
Collection<Throwable> failures = new ConcurrentLinkedQueue<>();
|
||||
final List<Callable<Void>> runnablesToTasks = new LinkedList<>();
|
||||
|
||||
// Track all used admin clients, so they can be closed after the test
|
||||
Set<Keycloak> usedKeycloaks = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
for (KeycloakRunnable runnable : runnables) {
|
||||
runnablesToTasks.add(() -> {
|
||||
int arrayIndex = currentThreadIndex.getAndIncrement() % numThreads;
|
||||
try {
|
||||
Keycloak keycloak = keycloaks.get();
|
||||
usedKeycloaks.add(keycloak);
|
||||
|
||||
runnable.run(arrayIndex % numThreads, keycloak, keycloak.realm(REALM_NAME));
|
||||
} catch (Throwable ex) {
|
||||
failures.add(ex);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
tasks.addAll(runnablesToTasks);
|
||||
|
||||
try {
|
||||
service.invokeAll(tasks);
|
||||
service.shutdown();
|
||||
service.awaitTermination(3, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} finally {
|
||||
for (Keycloak keycloak : usedKeycloaks) {
|
||||
try {
|
||||
keycloak.close();
|
||||
} catch (Exception e) {
|
||||
failures.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! failures.isEmpty()) {
|
||||
RuntimeException ex = new RuntimeException("There were failures in threads. Failures count: " + failures.size());
|
||||
failures.forEach(ex::addSuppressed);
|
||||
failures.forEach(e -> LOGGER.error(e.getMessage(), e));
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface KeycloakRunnable {
|
||||
|
||||
void run(int threadIndex, Keycloak keycloak, RealmResource realm) throws Throwable;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,10 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.admin.concurrency;
|
||||
package org.keycloak.tests.admin.concurrency;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
@@ -34,9 +33,10 @@ import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.model.StoreProvider;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.realm.UserConfigBuilder;
|
||||
import org.keycloak.tests.utils.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.userprofile.UserProfileUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -44,39 +44,35 @@ import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@KeycloakIntegrationTest
|
||||
public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
|
||||
public void concurrentTest(KeycloakRunnable... tasks) throws Throwable {
|
||||
System.out.println("***************************");
|
||||
long start = System.currentTimeMillis();
|
||||
run(tasks);
|
||||
long end = System.currentTimeMillis() - start;
|
||||
System.out.println("took " + end + " ms");
|
||||
}
|
||||
|
||||
// KEYCLOAK-8141 Verify that no attribute values are duplicated, and there are no locking exceptions when adding attributes in parallell
|
||||
// Verify that no attribute values are duplicated, and there are no locking exceptions when adding attributes in parallel
|
||||
// https://github.com/keycloak/keycloak/issues/38868
|
||||
@Test
|
||||
@Ignore
|
||||
public void createUserAttributes() throws Throwable {
|
||||
AtomicInteger c = new AtomicInteger();
|
||||
|
||||
UsersResource users = testRealm().users();
|
||||
UsersResource users = managedRealm.admin().users();
|
||||
|
||||
UserRepresentation u = UserBuilder.create().username("attributes").build();
|
||||
Response response = users.create(u);
|
||||
String userId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
// enable unmanaged attributes
|
||||
UserProfileUtil.enableUnmanagedAttributes(users.userProfile());
|
||||
|
||||
UserRepresentation u = UserConfigBuilder.create().username("attributes").build();
|
||||
|
||||
String userId;
|
||||
try (Response response = users.create(u)) {
|
||||
userId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
UserResource user = users.get(userId);
|
||||
|
||||
@@ -90,7 +86,6 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
|
||||
// Number of attributes should be equal to created attributes, or less (concurrent requests may drop attributes added by other threads)
|
||||
assertTrue(rep.getAttributes().size() <= c.get());
|
||||
|
||||
// All attributes should have a single value
|
||||
for (Map.Entry<String, List<String>> e : rep.getAttributes().entrySet()) {
|
||||
assertEquals(1, e.getValue().size());
|
||||
@@ -132,9 +127,10 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
public void createClientRole() throws Throwable {
|
||||
ClientRepresentation c = new ClientRepresentation();
|
||||
c.setClientId("client");
|
||||
Response response = adminClient.realm(REALM_NAME).clients().create(c);
|
||||
final String clientId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
final String clientId;
|
||||
try (Response response = managedRealm.admin().clients().create(c)) {
|
||||
clientId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
AtomicInteger uniqueCounter = new AtomicInteger();
|
||||
concurrentTest(new CreateClientRole(uniqueCounter, clientId));
|
||||
@@ -146,6 +142,14 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
run(new CreateRole(uniqueCounter));
|
||||
}
|
||||
|
||||
public void concurrentTest(KeycloakRunnable... tasks) throws Throwable {
|
||||
System.out.println("***************************");
|
||||
long start = System.currentTimeMillis();
|
||||
run(tasks);
|
||||
long end = System.currentTimeMillis() - start;
|
||||
System.out.println("took " + end + " ms");
|
||||
}
|
||||
|
||||
private class CreateClient implements KeycloakRunnable {
|
||||
|
||||
private final AtomicInteger clientIndex;
|
||||
@@ -156,27 +160,26 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
|
||||
@Override
|
||||
public void run(int threadIndex, Keycloak keycloak, RealmResource realm) throws Throwable {
|
||||
String name = "c-" + clientIndex.getAndIncrement();
|
||||
String name = "cc-" + clientIndex.getAndIncrement();
|
||||
ClientRepresentation c = new ClientRepresentation();
|
||||
c.setClientId(name);
|
||||
Response response = realm.clients().create(c);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
String id;
|
||||
try (Response response = realm.clients().create(c)) {
|
||||
id = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
c = realm.clients().get(id).toRepresentation();
|
||||
assertNotNull(c);
|
||||
|
||||
int findAttempts = 1;
|
||||
if (StoreProvider.getCurrentProvider().equals(StoreProvider.DEFAULT)) {
|
||||
findAttempts = 5;
|
||||
}
|
||||
int findAttempts = 5;
|
||||
|
||||
boolean clientFound = IntStream.range(0, findAttempts)
|
||||
.anyMatch(i -> realm.clients().findAll().stream()
|
||||
.map(ClientRepresentation::getClientId)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals));
|
||||
|
||||
assertTrue("Client " + name + " not found in client list after " + findAttempts + " attempts", clientFound);
|
||||
assertTrue(clientFound, "Client " + name + " not found in client list after " + findAttempts + " attempts");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,30 +193,29 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
|
||||
@Override
|
||||
public void run(int threadIndex, Keycloak keycloak, RealmResource realm) throws Throwable {
|
||||
String name = "c-" + clientIndex.getAndIncrement();
|
||||
String name = "crc-" + clientIndex.getAndIncrement();
|
||||
ClientRepresentation c = new ClientRepresentation();
|
||||
c.setClientId(name);
|
||||
final ClientsResource clients = realm.clients();
|
||||
|
||||
Response response = clients.create(c);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
String id;
|
||||
try (Response response = clients.create(c)) {
|
||||
id = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
final ClientResource client = clients.get(id);
|
||||
|
||||
c = client.toRepresentation();
|
||||
assertNotNull(c);
|
||||
|
||||
int findAttempts = 1;
|
||||
if (StoreProvider.getCurrentProvider().equals(StoreProvider.DEFAULT)) {
|
||||
findAttempts = 5;
|
||||
}
|
||||
int findAttempts = 5;
|
||||
|
||||
boolean clientFound = IntStream.range(0, findAttempts)
|
||||
.anyMatch(i -> clients.findAll().stream()
|
||||
.map(ClientRepresentation::getClientId)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals));
|
||||
.map(ClientRepresentation::getClientId)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals));
|
||||
|
||||
assertTrue("Client " + name + " not found in client list after " + findAttempts + " attempts", clientFound);
|
||||
assertTrue(clientFound, "Client " + name + " not found in client list after " + findAttempts + " attempts");
|
||||
|
||||
client.remove();
|
||||
try {
|
||||
@@ -223,11 +225,10 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
|
||||
}
|
||||
|
||||
assertFalse("Client " + name + " should now not present in client list",
|
||||
clients.findAll().stream()
|
||||
assertFalse(clients.findAll().stream()
|
||||
.map(ClientRepresentation::getClientId)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals));
|
||||
.anyMatch(name::equals), "Client " + name + " should now not present in client list");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,18 +245,18 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
||||
String name = "g-" + uniqueIndex.getAndIncrement();
|
||||
GroupRepresentation c = new GroupRepresentation();
|
||||
c.setName(name);
|
||||
Response response = realm.groups().add(c);
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
String id;
|
||||
try (Response response = realm.groups().add(c)) {
|
||||
id = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
c = realm.groups().group(id).toRepresentation();
|
||||
assertNotNull(c);
|
||||
|
||||
assertTrue("Group " + name + " [" + id + "] " + " not found in group list",
|
||||
realm.groups().groups().stream()
|
||||
.map(GroupRepresentation::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals));
|
||||
assertTrue(realm.groups().groups().stream()
|
||||
.map(GroupRepresentation::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals), "Group " + name + " [" + id + "] " + " not found in group list");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public abstract class AbstractConcurrencyTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
private static final int DEFAULT_THREADS = 4;
|
||||
|
||||
Reference in New Issue
Block a user