From 4dffc3cf7ed692943811f1ff5a9be6ddfa17e8c2 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 9 Feb 2016 16:32:09 +0100 Subject: [PATCH] Support for bulk adding of users in testsuite CLI --- .../org/keycloak/testsuite/InfinispanCLI.java | 243 ------------------ .../keycloak/testsuite/KeycloakServer.java | 1 + .../testsuite/util/cli/AbstractCommand.java | 89 +++++++ .../util/cli/AbstractOfflineCacheCommand.java | 198 ++++++++++++++ .../testsuite/util/cli/InfinispanCLI.java | 187 ++++++++++++++ .../cli/LoadPersistentSessionsCommand.java | 47 ++++ .../util/cli/PersistSessionsCommand.java | 125 +++++++++ .../testsuite/util/cli/UserCommands.java | 212 +++++++++++++++ 8 files changed, 859 insertions(+), 243 deletions(-) delete mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/InfinispanCLI.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractCommand.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/LoadPersistentSessionsCommand.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/InfinispanCLI.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/InfinispanCLI.java deleted file mode 100644 index 7cd7b5822f..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/InfinispanCLI.java +++ /dev/null @@ -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 Marek Posolda - */ -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 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 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 userSessionIds = new LinkedList<>(); - final List 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 ; iMarek Posolda + */ +public abstract class AbstractCommand { + + protected final Logger log = Logger.getLogger(this.getClass().getName()); + + protected List args; + protected KeycloakSessionFactory sessionFactory; + + public void injectProperties(List 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 { + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java new file mode 100644 index 0000000000..ae677b8a0f --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java @@ -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 Marek Posolda + */ +public abstract class AbstractOfflineCacheCommand extends AbstractCommand { + + @Override + protected void doRunCommand(KeycloakSession session) { + InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); + Cache 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 cache); + + + // IMPLS + + public static class PutCommand extends AbstractOfflineCacheCommand { + + @Override + public String getName() { + return "put"; + } + + @Override + protected void doRunCacheCommand(KeycloakSession session, Cache 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() + " "; + } + } + + + public static class GetCommand extends AbstractOfflineCacheCommand { + + @Override + public String getName() { + return "get"; + } + + @Override + protected void doRunCacheCommand(KeycloakSession session, Cache cache) { + String id = getArg(0); + UserSessionEntity userSession = (UserSessionEntity) cache.get(id); + printSession(id, userSession); + } + + @Override + public String printUsage() { + return getName() + " "; + } + } + + + public static class RemoveCommand extends AbstractOfflineCacheCommand { + + @Override + public String getName() { + return "remove"; + } + + @Override + protected void doRunCacheCommand(KeycloakSession session, Cache cache) { + String id = getArg(0); + cache.remove(id); + } + + @Override + public String printUsage() { + return getName() + " "; + } + } + + + public static class ClearCommand extends AbstractOfflineCacheCommand { + + @Override + public String getName() { + return "clear"; + } + + @Override + protected void doRunCacheCommand(KeycloakSession session, Cache cache) { + cache.clear(); + } + } + + + public static class SizeCommand extends AbstractOfflineCacheCommand { + + @Override + public String getName() { + return "size"; + } + + @Override + protected void doRunCacheCommand(KeycloakSession session, Cache 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 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 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() + " "; + } + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java new file mode 100644 index 0000000000..22b1026df3 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java @@ -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 Marek Posolda + */ +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> commands = new LinkedHashMap<>(); + + public InfinispanCLI(KeycloakServer server) { + this.sessionFactory = server.getSessionFactory(); + + // register builtin commands + for (Class clazz : BUILTIN_COMMANDS) { + Class commandClazz = (Class) 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 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 commandClass = commands.get(commandName); + if (commandClass == null) { + log.errorf("Unknown command: %s", commandName); + } else { + try { + AbstractCommand command = commandClass.newInstance(); + List 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 commandNames = new ArrayList<>(); + + @Override + public void injectProperties(List 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 + } + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/LoadPersistentSessionsCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/LoadPersistentSessionsCommand.java new file mode 100644 index 0000000000..0c1a506ec4 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/LoadPersistentSessionsCommand.java @@ -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 Marek Posolda + */ +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() + " "; + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java new file mode 100644 index 0000000000..7f67865fd8 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java @@ -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 Marek Posolda + */ +public class PersistSessionsCommand extends AbstractCommand { + + @Override + public String getName() { + return "persistSessions"; + } + + @Override + public void doRunCommand(KeycloakSession sess) { + final int count = getIntArg(0); + final List userSessionIds = new LinkedList<>(); + final List 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() + " "; + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java new file mode 100644 index 0000000000..950b9a4172 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java @@ -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 Marek Posolda + */ +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 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() + " . \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 findRoles(RealmModel realm, String rolesList) { + Set 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() + " \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() + " "; + } + } + + + 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() + " "; + } + } +}