Support for bulk adding of users in testsuite CLI
This commit is contained in:
parent
00236c13ff
commit
4dffc3cf7e
8 changed files with 859 additions and 243 deletions
|
@ -1,243 +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.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.infinispan.AdvancedCache;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionProviderFactory;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
/**
|
||||
* HOWTO USE THIS:
|
||||
*
|
||||
* 1) Run KeycloakServer with system properties (assuming mongo up and running on localhost):
|
||||
* -Dkeycloak.realm.provider=mongo -Dkeycloak.user.provider=mongo -Dkeycloak.userSessionPersister.provider=mongo -Dkeycloak.connectionsMongo.db=keycloak -Dkeycloak.connectionsInfinispan.clustered=true -Dresources -DstartInfinispanCLI
|
||||
*
|
||||
* 2) Write command on STDIN to persist 50000 userSessions to mongo: persistSessions 50000
|
||||
*
|
||||
* 3) Run command "clear" to ensure infinispan cache is cleared. Doublecheck with command "size" is 0
|
||||
*
|
||||
* 4) Write command to load sessions from persistent storage - 100 sessions per worker transaction: loadPersistentSessions 100
|
||||
*
|
||||
* See the progress in log. Finally run command "size" to ensure size is 100001 (50000 userSessions + 50000 clientSessions + 1 initializationState item)
|
||||
*
|
||||
* 5) Alternative to step 3+4 - Kill the server after step 2 and start two KeycloakServer in parallel on ports 8081 and 8082 . See the progress in logs of loading persistent sessions to infinispan.
|
||||
* Kill the coordinator (usually 8081 node) during startup and see the node 8082 became coordinator and took ownership of loading persistent sessions. After node 8082 fully started, the size of infinispan is again 100001
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class InfinispanCLI {
|
||||
|
||||
private static final Logger log = Logger.getLogger(InfinispanCLI.class);
|
||||
|
||||
private final KeycloakSessionFactory sessionFactory;
|
||||
|
||||
public InfinispanCLI(KeycloakServer server) {
|
||||
this.sessionFactory = server.getSessionFactory();
|
||||
}
|
||||
|
||||
// WARNING: Stdin blocking operation
|
||||
public void start() throws IOException {
|
||||
log.info("Starting infinispan CLI. Exit with 'exit'");
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
String line;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
log.info("Command: " + line);
|
||||
|
||||
if (line.equals("exit")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String finalLine = line;
|
||||
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, SessionEntity> ispnCache = provider.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||
runTask(finalLine, ispnCache);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
log.info("Exit infinispan CLI");
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void runTask(String line, Cache<String, SessionEntity> cache) {
|
||||
try {
|
||||
String[] splits = line.split(" ");
|
||||
if (splits[0].equals("put")) {
|
||||
UserSessionEntity userSession = new UserSessionEntity();
|
||||
String id = splits[1];
|
||||
|
||||
userSession.setId(id);
|
||||
userSession.setRealm(splits[2]);
|
||||
userSession.setLastSessionRefresh(Time.currentTime());
|
||||
cache.put(id, userSession);
|
||||
|
||||
} else if (splits[0].equals("get")) {
|
||||
String id = splits[1];
|
||||
UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
|
||||
printSession(id, userSession);
|
||||
} else if (splits[0].equals("remove")) {
|
||||
String id = splits[1];
|
||||
cache.remove(id);
|
||||
} else if (splits[0].equals("clear")) {
|
||||
cache.clear();
|
||||
log.info("Cache cleared");
|
||||
} else if (splits[0].equals("size")) {
|
||||
log.info("Size: " + cache.size());
|
||||
} else if (splits[0].equals("list")) {
|
||||
for (String id : cache.keySet()) {
|
||||
SessionEntity entity = cache.get(id);
|
||||
if (!(entity instanceof UserSessionEntity)) {
|
||||
continue;
|
||||
}
|
||||
UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
|
||||
log.info("list: key=" + id + ", value=" + toString(userSession));
|
||||
}
|
||||
|
||||
} else if (splits[0].equals("getLocal")) {
|
||||
String id = splits[1];
|
||||
cache = ((AdvancedCache) cache).withFlags(Flag.CACHE_MODE_LOCAL);
|
||||
UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
|
||||
printSession(id, userSession);
|
||||
|
||||
} else if (splits[0].equals("persistSessions")) {
|
||||
|
||||
final int count = Integer.parseInt(splits[1]);
|
||||
final List<String> userSessionIds = new LinkedList<>();
|
||||
final List<String> clientSessionIds = new LinkedList<>();
|
||||
|
||||
// Create sessions in separate transaction first
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName("master");
|
||||
UserModel john = session.users().getUserByUsername("admin", realm);
|
||||
ClientModel testApp = realm.getClientByClientId("security-admin-console");
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
for (int i=0 ; i<count ; i++) {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp);
|
||||
clientSession.setUserSession(userSession);
|
||||
clientSession.setRedirectUri("http://redirect");
|
||||
clientSession.setNote("foo", "bar-" + i);
|
||||
userSessionIds.add(userSession.getId());
|
||||
clientSessionIds.add(clientSession.getId());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
log.info("Sessions created in infinispan storage");
|
||||
|
||||
// Persist them now
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName("master");
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
for (String userSessionId : userSessionIds) {
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
|
||||
persister.createUserSession(userSession, true);
|
||||
}
|
||||
|
||||
log.info("userSessions persisted");
|
||||
|
||||
for (String clientSessionId : clientSessionIds) {
|
||||
ClientSessionModel clientSession = session.sessions().getClientSession(realm, clientSessionId);
|
||||
persister.createClientSession(clientSession, true);
|
||||
}
|
||||
|
||||
log.info("clientSessions persisted");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Persist them now
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName("master");
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
log.info(count + " sessions persisted. Total number of sessions: " + persister.getUserSessionsCount(true));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} else if (splits[0].equals("loadPersistentSessions")) {
|
||||
|
||||
int sessionsPerSegment = Integer.parseInt(splits[1]);
|
||||
UserSessionProviderFactory sessionProviderFactory = (UserSessionProviderFactory) sessionFactory.getProviderFactory(UserSessionProvider.class);
|
||||
sessionProviderFactory.loadPersistentSessions(sessionFactory, 10, sessionsPerSegment);
|
||||
|
||||
log.info("All persistent sessions loaded successfully");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Error occured during command. ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void printSession(String id, UserSessionEntity userSession) {
|
||||
if (userSession == null) {
|
||||
log.info("Not found session with Id: " + id);
|
||||
} else {
|
||||
log.info("Found session. ID: " + toString(userSession));
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(UserSessionEntity userSession) {
|
||||
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
|
||||
", clientSessions: " + userSession.getClientSessions().size();
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
|||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.testsuite.util.cli.InfinispanCLI;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.testsuite.util.cli;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractCommand {
|
||||
|
||||
protected final Logger log = Logger.getLogger(this.getClass().getName());
|
||||
|
||||
protected List<String> args;
|
||||
protected KeycloakSessionFactory sessionFactory;
|
||||
|
||||
public void injectProperties(List<String> args, InfinispanCLI cli, KeycloakSessionFactory sessionFactory) {
|
||||
this.args = args;
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
public void runCommand() {
|
||||
try {
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
doRunCommand(session);
|
||||
}
|
||||
|
||||
});
|
||||
} catch (HandledException handled) {
|
||||
// Fine to ignore. Was handled already
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Error occured during command. ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
protected abstract void doRunCommand(KeycloakSession session);
|
||||
|
||||
protected String getArg(int index) {
|
||||
try {
|
||||
return args.get(index);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
log.errorf("Usage: %s", printUsage());
|
||||
throw new HandledException();
|
||||
}
|
||||
}
|
||||
|
||||
protected Integer getIntArg(int index) {
|
||||
String str = getArg(index);
|
||||
try {
|
||||
return Integer.parseInt(str);
|
||||
} catch (NumberFormatException nex) {
|
||||
log.errorf("Usage: %s", printUsage());
|
||||
throw new HandledException();
|
||||
}
|
||||
}
|
||||
|
||||
public String printUsage() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public static class HandledException extends RuntimeException {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* 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.testsuite.util.cli;
|
||||
|
||||
import org.infinispan.AdvancedCache;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractOfflineCacheCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, SessionEntity> ispnCache = provider.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||
doRunCacheCommand(session, ispnCache);
|
||||
}
|
||||
|
||||
protected void printSession(String id, UserSessionEntity userSession) {
|
||||
if (userSession == null) {
|
||||
log.info("Not found session with Id: " + id);
|
||||
} else {
|
||||
log.info("Found session. ID: " + toString(userSession));
|
||||
}
|
||||
}
|
||||
|
||||
protected String toString(UserSessionEntity userSession) {
|
||||
int clientSessionsSize = userSession.getClientSessions()==null ? 0 : userSession.getClientSessions().size();
|
||||
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
|
||||
", clientSessions: " + clientSessionsSize;
|
||||
}
|
||||
|
||||
protected abstract void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache);
|
||||
|
||||
|
||||
// IMPLS
|
||||
|
||||
public static class PutCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "put";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
UserSessionEntity userSession = new UserSessionEntity();
|
||||
String id = getArg(0);
|
||||
|
||||
userSession.setId(id);
|
||||
userSession.setRealm(getArg(1));
|
||||
|
||||
userSession.setLastSessionRefresh(Time.currentTime());
|
||||
cache.put(id, userSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return getName() + " <user-session-id> <realm-name>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class GetCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "get";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
String id = getArg(0);
|
||||
UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
|
||||
printSession(id, userSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return getName() + " <user-session-id>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class RemoveCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "remove";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
String id = getArg(0);
|
||||
cache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return getName() + " <user-session-id>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ClearCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clear";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SizeCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "size";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
log.info("Size: " + cache.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ListCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "list";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
for (String id : cache.keySet()) {
|
||||
SessionEntity entity = cache.get(id);
|
||||
if (!(entity instanceof UserSessionEntity)) {
|
||||
continue;
|
||||
}
|
||||
UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
|
||||
log.info("list: key=" + id + ", value=" + toString(userSession));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class GetLocalCommand extends AbstractOfflineCacheCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "getLocal";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
|
||||
String id = getArg(0);
|
||||
cache = ((AdvancedCache) cache).withFlags(Flag.CACHE_MODE_LOCAL);
|
||||
UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
|
||||
printSession(id, userSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return getName() + " <user-session-id>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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.testsuite.util.cli;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.testsuite.KeycloakServer;
|
||||
|
||||
/**
|
||||
* HOWTO USE THIS:
|
||||
*
|
||||
* 1) Run KeycloakServer with system properties (assuming mongo up and running on localhost):
|
||||
* -Dkeycloak.realm.provider=mongo -Dkeycloak.user.provider=mongo -Dkeycloak.userSessionPersister.provider=mongo -Dkeycloak.connectionsMongo.db=keycloak -Dkeycloak.connectionsInfinispan.clustered=true -Dresources -DstartInfinispanCLI
|
||||
*
|
||||
* 2) Write command on STDIN to persist 50000 userSessions to mongo: persistSessions 50000
|
||||
*
|
||||
* 3) Run command "clear" to ensure infinispan cache is cleared. Doublecheck with command "size" is 0
|
||||
*
|
||||
* 4) Write command to load sessions from persistent storage - 100 sessions per worker transaction: loadPersistentSessions 100
|
||||
*
|
||||
* See the progress in log. Finally run command "size" to ensure size is 100001 (50000 userSessions + 50000 clientSessions + 1 initializationState item)
|
||||
*
|
||||
* 5) Alternative to step 3+4 - Kill the server after step 2 and start two KeycloakServer in parallel on ports 8081 and 8082 . See the progress in logs of loading persistent sessions to infinispan.
|
||||
* Kill the coordinator (usually 8081 node) during startup and see the node 8082 became coordinator and took ownership of loading persistent sessions. After node 8082 fully started, the size of infinispan is again 100001
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class InfinispanCLI {
|
||||
|
||||
private static final Logger log = Logger.getLogger(InfinispanCLI.class);
|
||||
|
||||
private static final Class<?>[] BUILTIN_COMMANDS = {
|
||||
ExitCommand.class,
|
||||
HelpCommand.class,
|
||||
AbstractOfflineCacheCommand.PutCommand.class,
|
||||
AbstractOfflineCacheCommand.GetCommand.class,
|
||||
AbstractOfflineCacheCommand.GetLocalCommand.class,
|
||||
AbstractOfflineCacheCommand.RemoveCommand.class,
|
||||
AbstractOfflineCacheCommand.SizeCommand.class,
|
||||
AbstractOfflineCacheCommand.ListCommand.class,
|
||||
AbstractOfflineCacheCommand.ClearCommand.class,
|
||||
PersistSessionsCommand.class,
|
||||
LoadPersistentSessionsCommand.class,
|
||||
UserCommands.Create.class,
|
||||
UserCommands.Remove.class,
|
||||
UserCommands.Count.class,
|
||||
UserCommands.GetUser.class
|
||||
};
|
||||
|
||||
private final KeycloakSessionFactory sessionFactory;
|
||||
private final Map<String, Class<? extends AbstractCommand>> commands = new LinkedHashMap<>();
|
||||
|
||||
public InfinispanCLI(KeycloakServer server) {
|
||||
this.sessionFactory = server.getSessionFactory();
|
||||
|
||||
// register builtin commands
|
||||
for (Class<?> clazz : BUILTIN_COMMANDS) {
|
||||
Class<? extends AbstractCommand> commandClazz = (Class<? extends AbstractCommand>) clazz;
|
||||
try {
|
||||
AbstractCommand command = commandClazz.newInstance();
|
||||
commands.put(command.getName(), commandClazz);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error registering command of class: " + commandClazz.getName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void registerCommand(String name, Class<? extends AbstractCommand> command) {
|
||||
commands.put(name, command);
|
||||
}
|
||||
|
||||
// WARNING: Stdin blocking operation
|
||||
public void start() throws IOException {
|
||||
log.info("Starting infinispan CLI. Exit with 'exit' . Available commands with 'help' ");
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
String line;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
log.info("Command: " + line);
|
||||
|
||||
String[] splits = line.split(" ");
|
||||
String commandName = splits[0];
|
||||
Class<? extends AbstractCommand> commandClass = commands.get(commandName);
|
||||
if (commandClass == null) {
|
||||
log.errorf("Unknown command: %s", commandName);
|
||||
} else {
|
||||
try {
|
||||
AbstractCommand command = commandClass.newInstance();
|
||||
List<String> args = new ArrayList<>(Arrays.asList(splits));
|
||||
args.remove(0);
|
||||
command.injectProperties(args, this, sessionFactory);
|
||||
command.runCommand();
|
||||
|
||||
// Just special handling of ExitCommand
|
||||
if (command instanceof ExitCommand) {
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (InstantiationException ex) {
|
||||
log.error(ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
log.error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
log.info("Exit infinispan CLI");
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExitCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "exit";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runCommand() {
|
||||
// no need to implement. Exit handled in parent
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
// no need to implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelpCommand extends AbstractCommand {
|
||||
|
||||
private List<String> commandNames = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void injectProperties(List<String> args, InfinispanCLI cli, KeycloakSessionFactory sessionFactory) {
|
||||
for (String commandName : cli.commands.keySet()) {
|
||||
commandNames.add(commandName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runCommand() {
|
||||
log.info("Available commands: " + commandNames.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
// no need to implement
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.testsuite.util.cli;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class LoadPersistentSessionsCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "loadPersistentSessions";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
int sessionsPerSegment = getIntArg(0);
|
||||
UserSessionProviderFactory sessionProviderFactory = (UserSessionProviderFactory) sessionFactory.getProviderFactory(UserSessionProvider.class);
|
||||
sessionProviderFactory.loadPersistentSessions(sessionFactory, 10, sessionsPerSegment);
|
||||
|
||||
log.info("All persistent sessions loaded successfully");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return super.printUsage() + " <sessions-count-per-segment>";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.testsuite.util.cli;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PersistSessionsCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "persistSessions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRunCommand(KeycloakSession sess) {
|
||||
final int count = getIntArg(0);
|
||||
final List<String> userSessionIds = new LinkedList<>();
|
||||
final List<String> clientSessionIds = new LinkedList<>();
|
||||
|
||||
// Create sessions in separate transaction first
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName("master");
|
||||
UserModel john = session.users().getUserByUsername("admin", realm);
|
||||
ClientModel testApp = realm.getClientByClientId("security-admin-console");
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp);
|
||||
clientSession.setUserSession(userSession);
|
||||
clientSession.setRedirectUri("http://redirect");
|
||||
clientSession.setNote("foo", "bar-" + i);
|
||||
userSessionIds.add(userSession.getId());
|
||||
clientSessionIds.add(clientSession.getId());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
log.info("Sessions created in infinispan storage");
|
||||
|
||||
// Persist them now
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName("master");
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
|
||||
int counter = 0;
|
||||
for (String userSessionId : userSessionIds) {
|
||||
counter++;
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
|
||||
persister.createUserSession(userSession, true);
|
||||
if (counter%1000 == 0) {
|
||||
log.infof("%d user sessions persisted. Continue", counter);
|
||||
}
|
||||
}
|
||||
|
||||
log.infof("All %d user sessions persisted", counter);
|
||||
|
||||
counter = 0;
|
||||
for (String clientSessionId : clientSessionIds) {
|
||||
counter++;
|
||||
ClientSessionModel clientSession = session.sessions().getClientSession(realm, clientSessionId);
|
||||
persister.createClientSession(clientSession, true);
|
||||
if (counter%1000 == 0) {
|
||||
log.infof("%d client sessions persisted. Continue", counter);
|
||||
}
|
||||
}
|
||||
|
||||
log.infof("All %d client sessions persisted", counter);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Persist them now
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
log.info("Total number of sessions in persister: " + persister.getUserSessionsCount(true));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return super.printUsage() + " <sessions-count>";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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.testsuite.util.cli;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserCommands {
|
||||
|
||||
public static class Create extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "createUsers";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
String usernamePrefix = getArg(0);
|
||||
String password = getArg(1);
|
||||
String realmName = getArg(2);
|
||||
int first = getIntArg(3);
|
||||
int count = getIntArg(4);
|
||||
String roleNames = getArg(5);
|
||||
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
if (realm == null) {
|
||||
log.errorf("Unknown realm: %s", realmName);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<RoleModel> roles = findRoles(realm, roleNames);
|
||||
|
||||
int last = first + count;
|
||||
for (int counter = first; counter < last; counter++) {
|
||||
String username = usernamePrefix + counter;
|
||||
UserModel user = session.users().addUser(realm, username);
|
||||
user.setEnabled(true);
|
||||
user.setEmail(username + "@keycloak.org");
|
||||
UserCredentialModel passwordCred = UserCredentialModel.password(password);
|
||||
user.updateCredential(passwordCred);
|
||||
|
||||
for (RoleModel role : roles) {
|
||||
user.grantRole(role);
|
||||
}
|
||||
}
|
||||
log.infof("Users from %s to %s created", usernamePrefix + first, usernamePrefix + (last - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return super.printUsage() + " <username-prefix> <password> <realm-name> <starting-user-offset> <count> <realm-roles-list>. \nRoles list is divided by comma (client roles not yet supported)>\n" +
|
||||
"Example usage: " + super.printUsage() + " test test demo 0 20 user,admin";
|
||||
}
|
||||
|
||||
private Set<RoleModel> findRoles(RealmModel realm, String rolesList) {
|
||||
Set<RoleModel> result = new HashSet<>();
|
||||
|
||||
String[] roles = rolesList.split(",");
|
||||
for (String roleName : roles) {
|
||||
roleName = roleName.trim();
|
||||
RoleModel role;
|
||||
if (roleName.contains("/")) {
|
||||
String[] spl = roleName.split("/");
|
||||
ClientModel client = realm.getClientByClientId(spl[0]);
|
||||
if (client == null) {
|
||||
log.errorf("Client not found: %s", spl[0]);
|
||||
throw new HandledException();
|
||||
}
|
||||
role = client.getRole(spl[1]);
|
||||
} else {
|
||||
role = realm.getRole(roleName);
|
||||
}
|
||||
|
||||
if (role == null) {
|
||||
log.errorf("Role not found: %s", roleName);
|
||||
throw new HandledException();
|
||||
}
|
||||
|
||||
result.add(role);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class Remove extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "removeUsers";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
String usernamePrefix = getArg(0);
|
||||
String realmName = getArg(1);
|
||||
int first = getIntArg(2);
|
||||
int count = getIntArg(3);
|
||||
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
if (realm == null) {
|
||||
log.errorf("Unknown realm: %s", realmName);
|
||||
return;
|
||||
}
|
||||
|
||||
int last = first + count;
|
||||
for (int counter = first; counter < last; counter++) {
|
||||
String username = usernamePrefix + counter;
|
||||
UserModel user = session.users().getUserByUsername(username, realm);
|
||||
if (user == null) {
|
||||
log.errorf("User '%s' not found", username);
|
||||
} else {
|
||||
session.users().removeUser(realm, user);
|
||||
}
|
||||
}
|
||||
log.infof("Users from %s to %s removed", usernamePrefix + first, usernamePrefix + (last - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return super.printUsage() + " <username-prefix> <realm-name> <starting-user-offset> <count> \n" +
|
||||
"Example usage: " + super.printUsage() + " test demo 0 20";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Count extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "getUsersCount";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
String realmName = getArg(0);
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
if (realm == null) {
|
||||
log.errorf("Unknown realm: %s", realmName);
|
||||
return;
|
||||
}
|
||||
|
||||
int usersCount = session.users().getUsersCount(realm);
|
||||
log.infof("Users count in realm %s: %d", realmName, usersCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return super.printUsage() + " <realm-name>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class GetUser extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "getUser";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRunCommand(KeycloakSession session) {
|
||||
String realmName = getArg(0);
|
||||
String username = getArg(1);
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
if (realm == null) {
|
||||
log.errorf("Unknown realm: %s", realmName);
|
||||
return;
|
||||
}
|
||||
|
||||
UserModel user = session.users().getUserByUsername(username, realm);
|
||||
if (user == null) {
|
||||
log.infof("User '%s' doesn't exist in realm '%s'", username, realmName);
|
||||
} else {
|
||||
log.infof("User: ID: '%s', username: '%s', mail: '%s'", user.getId(), user.getUsername(), user.getEmail());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printUsage() {
|
||||
return super.printUsage() + " <realm-name> <username>";
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue