diff --git a/testsuite/performance/README.datasets.md b/testsuite/performance/README.datasets.md
new file mode 100644
index 0000000000..2338be1bd9
--- /dev/null
+++ b/testsuite/performance/README.datasets.md
@@ -0,0 +1,46 @@
+# Keycloak Performance Testsuite - Generating datasets
+
+
+## Generating a set of datasets for multiple realms
+
+The first dataset is small and is created quickly. Building of each subsequent dataset continues on top
+of the previous dataset.
+
+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.
+
+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.
+
+```
+cd testsuite/performance
+
+mvn clean install -Dserver.version=4.0.0.Beta1
+
+mvn verify -Pteardown
+mvn verify -Pprovision
+mvn verify -Pgenerate-data -Ddataset=10r100u1c -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=10r100u1c
+
+mvn verify -Pgenerate-data -Ddataset=20r100u1c -DstartAtRealmIdx=10 -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=20r100u1c
+
+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
+
+mvn verify -Pgenerate-data -Ddataset=500r100u1c -DstartAtRealmIdx=200 -DnumOfWorkers=10
+mvn verify -Pexport-dump -Ddataset=500r100u1c
+```
+
+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
+
+```
diff --git a/testsuite/performance/tests/docker-compose.sh b/testsuite/performance/tests/docker-compose.sh
index af8cd5e80b..d9b11b1772 100755
--- a/testsuite/performance/tests/docker-compose.sh
+++ b/testsuite/performance/tests/docker-compose.sh
@@ -399,7 +399,7 @@ case "$OPERATION" in
fi
echo "Importing $DATASET.sql.gz"
set -o pipefail
- if ! zcat $DATASET.sql.gz | docker exec -i $DB_CONTAINER /usr/bin/mysql -u root --password=root keycloak ; then
+ if ! gunzip -c $DATASET.sql.gz | docker exec -i $DB_CONTAINER /usr/bin/mysql -u root --password=root keycloak ; then
echo Import failed.
exit 1
fi
diff --git a/testsuite/performance/tests/parameters/datasets/10r100u1c.properties b/testsuite/performance/tests/parameters/datasets/10r100u1c.properties
new file mode 100644
index 0000000000..68a3ae245a
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/10r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=10
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/200r100u1c.properties b/testsuite/performance/tests/parameters/datasets/200r100u1c.properties
new file mode 100644
index 0000000000..4b8cf98337
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/200r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=200
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/20r100u1c.properties b/testsuite/performance/tests/parameters/datasets/20r100u1c.properties
new file mode 100644
index 0000000000..4296e870ab
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/20r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=20
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/500r100u1c.properties b/testsuite/performance/tests/parameters/datasets/500r100u1c.properties
new file mode 100644
index 0000000000..2f4c1643ea
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/500r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=500
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/parameters/datasets/50r100u1c.properties b/testsuite/performance/tests/parameters/datasets/50r100u1c.properties
new file mode 100644
index 0000000000..b5c3475760
--- /dev/null
+++ b/testsuite/performance/tests/parameters/datasets/50r100u1c.properties
@@ -0,0 +1,8 @@
+numOfRealms=50
+usersPerRealm=100
+clientsPerRealm=1
+realmRoles=100
+realmRolesPerUser=50
+clientRolesPerUser=0
+clientRolesPerClient=0
+hashIterations=27500
\ No newline at end of file
diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml
index 2fc997b774..5d9a0e1505 100644
--- a/testsuite/performance/tests/pom.xml
+++ b/testsuite/performance/tests/pom.xml
@@ -185,6 +185,7 @@
read-project-properties
+ true
${provisioning.properties.file}
${dataset.properties.file}
@@ -456,6 +457,12 @@
generate-data
+
+ 0
+ false
+ false
+ false
+
@@ -504,6 +511,10 @@
-DauthUser=${keycloak.admin.user}
-DauthPassword=${keycloak.admin.password}
-DnumOfWorkers=${numOfWorkers}
+ -DstartAtRealmIdx=${startAtRealmIdx}
+ -DignoreConflicts=${ignoreConflicts}
+ -DskipRealmRoles=${skipRealmRoles}
+ -DskipClientRoles=${skipClientRoles}
org.keycloak.performance.RealmsConfigurationLoader
benchmark-realms.json
diff --git a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java
index 52d1fde934..2597899451 100644
--- a/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java
+++ b/testsuite/performance/tests/src/main/java/org/keycloak/performance/RealmsConfigurationLoader.java
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jboss.logging.Logger;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -12,6 +13,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
+import javax.ws.rs.ClientErrorException;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
@@ -28,24 +30,37 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import static org.keycloak.performance.RealmsConfigurationBuilder.EXPORT_FILENAME;
+import static org.keycloak.performance.TestConfig.ignoreConflicts;
import static org.keycloak.performance.TestConfig.numOfWorkers;
+import static org.keycloak.performance.TestConfig.skipClientRoles;
+import static org.keycloak.performance.TestConfig.skipRealmRoles;
+import static org.keycloak.performance.TestConfig.startAtRealmIdx;
+import static org.keycloak.performance.TestConfig.startAtUserIdx;
/**
* # build
- * mvn -f testsuite/integration-arquillian/tests/performance/gatling-perf clean install
+ * mvn -f testsuite/performance/tests clean install
*
* # generate benchmark-realms.json file with generated test data
- * mvn -f testsuite/integration-arquillian/tests/performance/gatling-perf exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationBuilder -DnumOfRealms=2 -DusersPerRealm=2 -DclientsPerRealm=2 -DrealmRoles=2 -DrealmRolesPerUser=2 -DclientRolesPerUser=2 -DclientRolesPerClient=2
+ * mvn -f testsuite/performance/tests exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationBuilder -DnumOfRealms=2 -DusersPerRealm=2 -DclientsPerRealm=2 -DrealmRoles=2 -DrealmRolesPerUser=2 -DclientRolesPerUser=2 -DclientRolesPerClient=2
*
* # use benchmark-realms.json to load the data up to Keycloak Server listening on localhost:8080
- * mvn -f testsuite/integration-arquillian/tests/performance/gatling-perf exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationLoader -DnumOfWorkers=5 -Dexec.args=benchmark-realms.json > perf-output.txt
+ * mvn -f testsuite/performance/tests exec:java -Dexec.mainClass=org.keycloak.performance.RealmsConfigurationLoader -DnumOfWorkers=5 -Dexec.args=benchmark-realms.json > perf-output.txt
*
* @author Marko Strukelj
*/
public class RealmsConfigurationLoader {
+ static Logger log = Logger.getLogger(RealmsConfigurationLoader.class.getName());
+
static final int ERROR_CHECK_INTERVAL = 10;
+ static int currentRealm = 0;
+ static int currentUser = 0;
+ static int currentClient = 0;
+
+ static boolean started;
+
// multi-thread mechanics
static final BlockingQueue queue = new LinkedBlockingQueue<>(numOfWorkers);
static final ArrayList workers = new ArrayList<>();
@@ -58,24 +73,28 @@ public class RealmsConfigurationLoader {
static boolean realmCreated;
public static void main(String [] args) throws IOException {
- System.out.println("Keycloak servers: "+TestConfig.serverUrisList);
+ println("Keycloak servers: "+TestConfig.serverUrisList);
if (args.length == 0) {
args = new String[] {EXPORT_FILENAME};
}
if (args.length != 1) {
- System.out.println("Usage: java " + RealmsConfigurationLoader.class.getName() + " ");
+ println("Usage: java " + RealmsConfigurationLoader.class.getName() + " ");
return;
}
String file = args[0];
- System.out.println("Using file: " + new File(args[0]).getAbsolutePath());
- System.out.println("Number of workers (numOfWorkers): " + numOfWorkers);
+ println("Using file: " + new File(args[0]).getAbsolutePath());
+ println("Number of workers (numOfWorkers): " + numOfWorkers);
+ println("Parameters: ");
+ println(" startAtRealmIdx: " + startAtRealmIdx);
+// println(" startAtUserIdx: " + startAtUserIdx);
JsonParser p = initParser(file);
initWorkers();
+ initProgress();
try {
@@ -88,6 +107,28 @@ public class RealmsConfigurationLoader {
}
}
+ private static void initProgress() {
+ Thread t = new Thread(() -> {
+
+ for (;;) {
+ try {
+ Thread.sleep(60000);
+ println("At realm: " + currentRealm + ", Clients: " + currentClient + ", Users: " + currentUser);
+ } catch (InterruptedException e) {
+ return;
+ }
+
+ }
+
+ },"Progress Logger");
+ t.setDaemon(true);
+ t.start();
+ }
+
+ private static void println(String s) {
+ System.out.println(s);
+ }
+
private static void completeWorkers() {
try {
@@ -101,7 +142,7 @@ public class RealmsConfigurationLoader {
try {
w.join(5000);
if (w.isAlive()) {
- System.out.println("Worker thread failed to stop: ");
+ println("Worker thread failed to stop: ");
dumpThread(w);
}
} catch (InterruptedException e) {
@@ -117,6 +158,7 @@ public class RealmsConfigurationLoader {
while (t != JsonToken.END_OBJECT && t != JsonToken.END_ARRAY) {
if (t != JsonToken.START_ARRAY) {
readRealm(p);
+ currentRealm += 1;
}
t = p.nextToken();
}
@@ -150,7 +192,7 @@ public class RealmsConfigurationLoader {
for (StackTraceElement e: w.getStackTrace()) {
b.append(e.toString()).append("\n");
}
- System.out.print(b);
+ println(b.toString());
}
private static void readRealm(JsonParser p) throws IOException {
@@ -158,57 +200,112 @@ public class RealmsConfigurationLoader {
// as soon as we encounter users, roles, clients we create a CreateRealmJob
// TODO: if after that point in a realm we encounter realm attribute, we report a warning but continue
- RealmRepresentation r = new RealmRepresentation();
- JsonToken t = p.nextToken();
- while (t != JsonToken.END_OBJECT) {
+ boolean skip = false;
+ try {
+ RealmRepresentation r = new RealmRepresentation();
+ JsonToken t = p.nextToken();
+ outer:
+ while (t != JsonToken.END_OBJECT && !skip) {
- //System.out.println(t + ", name: " + p.getCurrentName() + ", text: '" + p.getText() + "', value: " + p.getValueAsString());
+ //System.out.println(t + ", name: " + p.getCurrentName() + ", text: '" + p.getText() + "', value: " + p.getValueAsString());
- switch (p.getCurrentName()) {
- case "realm":
- r.setRealm(getStringValue(p));
- break;
- case "enabled":
- r.setEnabled(getBooleanValue(p));
- break;
- case "accessTokenLifespan":
- r.setAccessCodeLifespan(getIntegerValue(p));
- break;
- case "registrationAllowed":
- r.setRegistrationAllowed(getBooleanValue(p));
- break;
- case "passwordPolicy":
- r.setPasswordPolicy(getStringValue(p));
- break;
- case "users":
- ensureRealm(r);
- readUsers(r, p);
- break;
- case "roles":
- ensureRealm(r);
- readRoles(r, p);
- break;
- case "clients":
- ensureRealm(r);
- readClients(r, p);
- break;
- default: {
- // if we don't understand the field we ignore it - but report that
- System.out.println("Realm attribute ignored: " + p.getCurrentName());
- consumeAttribute(p);
+ switch (p.getCurrentName()) {
+ case "realm":
+ r.setRealm(getStringValue(p));
+ skip = !started && realmSkipped(r.getRealm()) ;
+ if (skip) {
+ break outer;
+ }
+ break;
+ case "enabled":
+ r.setEnabled(getBooleanValue(p));
+ break;
+ case "accessTokenLifespan":
+ r.setAccessCodeLifespan(getIntegerValue(p));
+ break;
+ case "registrationAllowed":
+ r.setRegistrationAllowed(getBooleanValue(p));
+ break;
+ case "passwordPolicy":
+ r.setPasswordPolicy(getStringValue(p));
+ break;
+ case "sslRequired":
+ r.setSslRequired(getStringValue(p));
+ break;
+ case "users":
+ ensureRealm(r);
+ if (seekToStart()) {
+ enqueueFetchRealmRoles(r);
+ completePending();
+ }
+ readUsers(r, p);
+ break;
+ case "roles":
+ ensureRealm(r);
+ readRoles(r, p);
+ break;
+ case "clients":
+ ensureRealm(r);
+ readClients(r, p);
+ completePending();
+ if (seekToStart()) {
+ enqueueFetchMissingClients(r);
+ completePending();
+ }
+ break;
+ default: {
+ // if we don't understand the field we ignore it - but report that
+ log.warn("Realm attribute ignored: " + p.getCurrentName());
+ consumeAttribute(p);
+ continue; // skip p.nextToken() at end of loop - consumeAttribute() already did it
+ }
}
+
+ t = p.nextToken();
}
- t = p.nextToken();
+
+ if (skip) {
+ log.info("Realm skipped: " + r.getRealm());
+ consumeParent(p);
+ }
+
+ } finally {
+ // we wait for realm to complete
+ completePending();
+
+ // reset realm specific cache
+ realmCreated = false;
+ clientIdMap.clear();
+ realmRoleIdMap.clear();
+ clientRoleIdMap.clear();
}
+ }
- // we wait for realm to complete
- completePending();
+ private static void consumeParent(JsonParser p) throws IOException {
+ while (p.currentToken() != JsonToken.END_OBJECT) {
+ consumeAttribute(p);
+ }
+ }
- // reset realm specific cache
- realmCreated = false;
- clientIdMap.clear();
- realmRoleIdMap.clear();
- clientRoleIdMap.clear();
+ private static boolean seekToStart() {
+ return startAtRealmIdx > 0 || startAtUserIdx > 0;
+ }
+
+ private static boolean seeking() {
+ return currentRealm < startAtRealmIdx || currentUser < startAtUserIdx;
+ }
+
+ private static boolean realmSkipped(String realm) {
+ int pos = realm.lastIndexOf("_");
+ int idx = Integer.parseInt(realm.substring(pos+1));
+ return idx < startAtRealmIdx;
+ }
+
+ private static boolean userSkipped(String username) {
+ int pos = username.indexOf("_");
+ int end = username.indexOf("_", pos+1);
+ int idx = Integer.parseInt(username.substring(pos+1, end));
+ return idx < startAtUserIdx;
}
private static void ensureRealm(RealmRepresentation r) {
@@ -220,34 +317,18 @@ public class RealmsConfigurationLoader {
private static void createRealm(RealmRepresentation r) {
try {
+ started = true;
queue.put(new CreateRealmJob(r));
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
- // now wait for job to appear
- PendingResult next = pendingResult.poll();
- while (next == null) {
- waitForAwhile();
- next = pendingResult.poll();
- }
-
- // then wait for the job to complete
- while (!next.isDone()) {
- waitForAwhile();
- }
-
- try {
- next.get();
- } catch (InterruptedException e) {
- throw new RuntimeException("Interrupted", e);
- } catch (ExecutionException e) {
- throw new RuntimeException("Execution failed", e.getCause());
- }
+ completePending();
}
private static void enqueueCreateUser(RealmRepresentation r, UserRepresentation u) {
try {
+ started = true;
queue.put(new CreateUserJob(r, u));
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
@@ -256,6 +337,7 @@ public class RealmsConfigurationLoader {
private static void enqueueCreateRealmRole(RealmRepresentation r, RoleRepresentation role) {
try {
+ started = true;
queue.put(new CreateRealmRoleJob(r, role));
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
@@ -264,6 +346,7 @@ public class RealmsConfigurationLoader {
private static void enqueueCreateClientRole(RealmRepresentation r, RoleRepresentation role, String client) {
try {
+ started = true;
queue.put(new CreateClientRoleJob(r, role, client));
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
@@ -272,12 +355,31 @@ public class RealmsConfigurationLoader {
private static void enqueueCreateClient(RealmRepresentation r, ClientRepresentation client) {
try {
+ started = true;
queue.put(new CreateClientJob(r, client));
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
}
+ private static void enqueueFetchMissingClients(RealmRepresentation r) {
+ try {
+ started = true;
+ queue.put(new FetchMissingClientsJob(r));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted", e);
+ }
+ }
+
+ private static void enqueueFetchRealmRoles(RealmRepresentation r) {
+ try {
+ started = true;
+ queue.put(new FetchRealmRolesJob(r));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted", e);
+ }
+ }
+
private static void waitForAwhile() {
waitForAwhile(100, "Interrupted");
}
@@ -299,18 +401,22 @@ public class RealmsConfigurationLoader {
if (t != JsonToken.START_ARRAY) {
throw new RuntimeException("Error reading field 'users'. Expected array of users [" + t + "]");
}
- int count = 0;
+
t = p.nextToken();
while (t == JsonToken.START_OBJECT) {
UserRepresentation u = p.readValueAs(UserRepresentation.class);
- enqueueCreateUser(r, u);
+ if (!started && userSkipped(u.getUsername())) {
+ log.info("User skipped: " + u.getUsername());
+ } else {
+ enqueueCreateUser(r, u);
+ }
t = p.nextToken();
- count += 1;
+ currentUser += 1;
// every some users check to see pending errors
// in order to short-circuit if any errors have occurred
- if (count % ERROR_CHECK_INTERVAL == 0) {
- checkPendingErrors();
+ if (currentUser % ERROR_CHECK_INTERVAL == 0) {
+ checkPendingErrors(u.getUsername());
}
}
}
@@ -367,14 +473,16 @@ public class RealmsConfigurationLoader {
t = p.nextToken();
while (t != JsonToken.END_ARRAY) {
RoleRepresentation u = p.readValueAs(RoleRepresentation.class);
- enqueueCreateClientRole(r, u, client);
+ if (!seeking() || !skipClientRoles) {
+ enqueueCreateClientRole(r, u, client);
+ }
t = p.nextToken();
count += 1;
// every some roles check to see pending errors
// in order to short-circuit if any errors have occurred
if (count % ERROR_CHECK_INTERVAL == 0) {
- checkPendingErrors();
+ checkPendingErrors(u.getName());
}
}
t = p.nextToken();
@@ -393,14 +501,16 @@ public class RealmsConfigurationLoader {
int count = 0;
while (t == JsonToken.START_OBJECT) {
RoleRepresentation u = p.readValueAs(RoleRepresentation.class);
- enqueueCreateRealmRole(r, u);
+ if (!seeking() || !skipRealmRoles) {
+ enqueueCreateRealmRole(r, u);
+ }
t = p.nextToken();
count += 1;
// every some roles check to see pending errors
// in order to short-circuit if any errors have occurred
if (count % ERROR_CHECK_INTERVAL == 0) {
- checkPendingErrors();
+ checkPendingErrors(u.getName());
}
}
}
@@ -410,25 +520,25 @@ public class RealmsConfigurationLoader {
if (t != JsonToken.START_ARRAY) {
throw new RuntimeException("Error reading field 'clients'. Expected array of clients [" + t + "]");
}
- int count = 0;
+
t = p.nextToken();
while (t == JsonToken.START_OBJECT) {
ClientRepresentation u = p.readValueAs(ClientRepresentation.class);
enqueueCreateClient(r, u);
t = p.nextToken();
- count += 1;
+ currentClient += 1;
// every some users check to see pending errors
- if (count % ERROR_CHECK_INTERVAL == 0) {
- checkPendingErrors();
+ if (currentClient % ERROR_CHECK_INTERVAL == 0) {
+ checkPendingErrors(u.getClientId());
}
}
}
- private static void checkPendingErrors() {
+ private static void checkPendingErrors(String label) {
// now wait for job to appear
PendingResult next = pendingResult.peek();
- while (next == null) {
+ while (next == null && queue.size() > 0) {
waitForAwhile();
next = pendingResult.peek();
}
@@ -445,7 +555,7 @@ public class RealmsConfigurationLoader {
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
} catch (ExecutionException e) {
- throw new RuntimeException("Execution failed", e.getCause());
+ throw new RuntimeException("Execution failed in the vicinity of " + label + ": ", e.getCause());
}
}
}
@@ -479,9 +589,15 @@ public class RealmsConfigurationLoader {
}
private static void consumeAttribute(JsonParser p) throws IOException {
- JsonToken t = p.nextToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.START_OBJECT || t == JsonToken.START_ARRAY) {
p.skipChildren();
+ p.nextToken();
+ } else if (t == JsonToken.FIELD_NAME) {
+ p.nextToken();
+ consumeAttribute(p);
+ } else {
+ p.nextToken();
}
}
@@ -528,6 +644,40 @@ public class RealmsConfigurationLoader {
}
}
+ static class FetchMissingClientsJob extends AdminJob {
+
+ private RealmRepresentation realm;
+
+ FetchMissingClientsJob(RealmRepresentation r) {
+ realm = r;
+ }
+
+ @Override
+ public void run() {
+ List clients = admin().realms().realm(realm.getRealm()).clients().findAll();
+ for (ClientRepresentation c: clients) {
+ clientIdMap.put(c.getClientId(), c.getId());
+ }
+ }
+ }
+
+ static class FetchRealmRolesJob extends AdminJob {
+
+ private RealmRepresentation realm;
+
+ FetchRealmRolesJob(RealmRepresentation r) {
+ realm = r;
+ }
+
+ @Override
+ public void run() {
+ List roles = admin().realms().realm(realm.getRealm()).roles().list();
+ for (RoleRepresentation r: roles) {
+ realmRoleIdMap.put(r.getName(), r.getId());
+ }
+ }
+ }
+
static class CreateRealmJob extends AdminJob {
private RealmRepresentation realm;
@@ -538,7 +688,15 @@ public class RealmsConfigurationLoader {
@Override
public void run() {
- admin().realms().create(realm);
+ try {
+ admin().realms().create(realm);
+ } catch (ClientErrorException e) {
+ if (e.getMessage().endsWith("409 Conflict") && ignoreConflicts) {
+ log.warn("Ignoring conflict when creating a realm: " + realm.getRealm());
+ return;
+ }
+ throw e;
+ }
}
}
@@ -556,11 +714,17 @@ public class RealmsConfigurationLoader {
public void run() {
Response response = admin().realms().realm(realm.getRealm()).users().create(user);
response.close();
- if (response.getStatus() != 201) {
- throw new RuntimeException("Failed to create user with status: " + response.getStatusInfo().getReasonPhrase());
+
+ if (response.getStatus() == 409 && ignoreConflicts) {
+ log.warn("Ignoring conflict when creating a user: " + user.getUsername());
+ user.setId(admin().realms().realm(realm.getRealm()).users().search(user.getUsername()).get(0).getId());
+ } else if (response.getStatus() == 201) {
+ user.setId(extractIdFromResponse(response));
+ } else {
+ throw new RuntimeException("Failed to create user with status: " + response.getStatusInfo());
}
- String userId = extractIdFromResponse(response);
+ String userId = user.getId();
List creds = user.getCredentials();
for (CredentialRepresentation cred: creds) {
@@ -641,8 +805,16 @@ public class RealmsConfigurationLoader {
@Override
public void run() {
- admin().realms().realm(realm.getRealm()).roles().create(role);
-
+ try {
+ admin().realms().realm(realm.getRealm()).roles().create(role);
+ } catch (ClientErrorException e) {
+ if (e.getMessage().endsWith("409 Conflict") && ignoreConflicts) {
+ log.warn("Ignoring conflict when creating a realm role: " + role.getName());
+ role = admin().realms().realm(realm.getRealm()).roles().get(role.getName()).toRepresentation();
+ } else {
+ throw e;
+ }
+ }
// we need the id but it's not returned by REST API - we have to perform a get on the created role and save the returned id
RoleRepresentation rr = admin().realms().realm(realm.getRealm()).roles().get(role.getName()).toRepresentation();
realmRoleIdMap.put(rr.getName(), rr.getId());
@@ -668,7 +840,18 @@ public class RealmsConfigurationLoader {
if (id == null) {
throw new RuntimeException("No client created for clientId: " + clientId);
}
- admin().realms().realm(realm.getRealm()).clients().get(id).roles().create(role);
+
+ try {
+ admin().realms().realm(realm.getRealm()).clients().get(id).roles().create(role);
+
+ } catch (ClientErrorException e) {
+ if (e.getMessage().endsWith("409 Conflict") && ignoreConflicts) {
+ log.warn("Ignoring conflict when creating a client role: " + role.getName());
+ role = admin().realms().realm(realm.getRealm()).clients().get(id).roles().get(role.getName()).toRepresentation();
+ } else {
+ throw e;
+ }
+ }
// we need the id but it's not returned by REST API - we have to perform a get on the created role and save the returned id
RoleRepresentation rr = admin().realms().realm(realm.getRealm()).clients().get(id).roles().get(role.getName()).toRepresentation();
@@ -680,6 +863,7 @@ public class RealmsConfigurationLoader {
roleIdMap.put(rr.getName(), rr.getId());
}
+
}
static class CreateClientJob extends AdminJob {
@@ -697,11 +881,16 @@ public class RealmsConfigurationLoader {
public void run() {
Response response = admin().realms().realm(realm.getRealm()).clients().create(client);
response.close();
- if (response.getStatus() != 201) {
+
+ if (response.getStatus() == 409 && ignoreConflicts) {
+ log.warn("Ignoring conflict when creating a client: " + client.getClientId());
+ client = admin().realms().realm(realm.getRealm()).clients().findByClientId(client.getClientId()).get(0);
+ } else if (response.getStatus() == 201) {
+ client.setId(extractIdFromResponse(response));
+ } else {
throw new RuntimeException("Failed to create client with status: " + response.getStatusInfo().getReasonPhrase());
}
- String id = extractIdFromResponse(response);
- clientIdMap.put(client.getClientId(), id);
+ clientIdMap.put(client.getClientId(), client.getId());
}
}
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 5b6a11a4b8..94824d71bb 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
@@ -34,6 +34,11 @@ public class TestConfig {
// Settings used by RealmsConfigurationLoader only - when loading data into Keycloak
//
public static final int numOfWorkers = Integer.getInteger("numOfWorkers", 1);
+ public static final int startAtRealmIdx = Integer.getInteger("startAtRealmIdx", 0);
+ public static final int startAtUserIdx = 0; // doesn't work properly, will be removed later //Integer.getInteger("startAtUserIdx", 0);
+ public static final boolean ignoreConflicts = "true".equals(System.getProperty("ignoreConflicts", "false"));
+ public static final boolean skipRealmRoles = "true".equals(System.getProperty("skipRealmRoles", "false"));
+ public static final boolean skipClientRoles = "true".equals(System.getProperty("skipClientRoles", "false"));
//
// Settings used by RealmConfigurationLoader to connect to Admin REST API
diff --git a/testsuite/performance/tests/src/main/resources/logback.xml b/testsuite/performance/tests/src/main/resources/logback.xml
new file mode 100644
index 0000000000..153511f2e8
--- /dev/null
+++ b/testsuite/performance/tests/src/main/resources/logback.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ true
+
+
+
+
+ %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file