From c4bb29b4bb2549851e78e18fbe6cbfd725b7f43c Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 14 Aug 2017 08:27:01 +0200 Subject: [PATCH] KEYCLOAK-4187 SessionExpirationCrossDCTest - added tests for user logout and removal --- .../infinispan/changes/SessionUpdateTask.java | 2 - .../ConcurrencyJDGSessionsCacheTest.java | 4 + .../infinispan/TestCacheManagerFactory.java | 7 +- .../arquillian/AuthServerTestEnricher.java | 3 +- .../crossdc/SessionExpirationCrossDCTest.java | 76 +++++++++++++++++++ .../util/cli/AbstractSessionCacheCommand.java | 18 +++-- 6 files changed, 97 insertions(+), 13 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java index 66f88baa05..b79ea16b64 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionUpdateTask.java @@ -17,8 +17,6 @@ package org.keycloak.models.sessions.infinispan.changes; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; import org.keycloak.models.sessions.infinispan.entities.SessionEntity; /** diff --git a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java index dd34b19d9f..825d05a224 100644 --- a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java +++ b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGSessionsCacheTest.java @@ -244,6 +244,10 @@ public class ConcurrencyJDGSessionsCacheTest { SessionEntity session = (SessionEntity) remoteCache.get(cacheKey); SessionEntityWrapper sessionWrapper = new SessionEntityWrapper(session); + if (listenerCount.get() % 100 == 0) { + logger.infof("Listener count: " + listenerCount.get()); + } + // TODO: for distributed caches, ensure that it is executed just on owner OR if event.isCommandRetried origCache .getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE) diff --git a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/TestCacheManagerFactory.java b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/TestCacheManagerFactory.java index 06dd95fed5..6b4eec11e4 100644 --- a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/TestCacheManagerFactory.java +++ b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/TestCacheManagerFactory.java @@ -60,8 +60,9 @@ class TestCacheManagerFactory { private Configuration getCacheBackedByRemoteStore(int threadId, String cacheName, Class builderClass) { ConfigurationBuilder cacheConfigBuilder = new ConfigurationBuilder(); + String host = "localhost"; int port = threadId==1 ? 12232 : 13232; - //int port = 12232; + //int port = 11222; return cacheConfigBuilder.persistence().addStore(builderClass) .fetchPersistentState(false) @@ -74,8 +75,8 @@ class TestCacheManagerFactory { .forceReturnValues(false) .marshaller(KeycloakHotRodMarshallerFactory.class.getName()) .addServer() - .host("localhost") - .port(port) + .host(host) + .port(port) .connectionPool() .maxActive(20) .exhaustedAction(ExhaustedAction.CREATE_NEW) diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java index dc03248ef0..f03a81d126 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java @@ -173,7 +173,8 @@ public class AuthServerTestEnricher { if (suiteContext.getDcAuthServerBackendsInfo().stream().anyMatch(List::isEmpty)) { throw new RuntimeException(String.format("Some data center has no auth server container matching '%s' defined in arquillian.xml.", AUTH_SERVER_BACKEND)); } - if (suiteContext.getCacheServersInfo().isEmpty()) { + boolean cacheServerLifecycleSkip = Boolean.parseBoolean(System.getProperty("cache.server.lifecycle.skip")); + if (suiteContext.getCacheServersInfo().isEmpty() && !cacheServerLifecycleSkip) { throw new IllegalStateException("Cache containers misconfiguration"); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java index 96a59d84e0..19e45d1b62 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java @@ -24,11 +24,13 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.Constants; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Retry; import org.keycloak.testsuite.admin.ApiUtil; @@ -264,6 +266,80 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest { } + // USER OPERATIONS + + @Test + public void testUserRemoveSessions( + @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, + @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, + @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { + createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics); + +// log.infof("Sleeping!"); +// Thread.sleep(10000000); + + channelStatisticsCrossDc.reset(); + + // Remove test user + ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").remove(); + + + // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. + assertStatisticsExpected("After user remove", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, + sessions01, sessions02, remoteSessions01, remoteSessions02, 40l); + } + + + @Test + public void testUserRemoveOfflineSessions( + @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, + @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, + @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { + createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics); + +// log.infof("Sleeping!"); +// Thread.sleep(10000000); + + channelStatisticsCrossDc.reset(); + + // Remove test user + ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").remove(); + + + // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. + assertStatisticsExpected("After user remove", InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, + sessions01, sessions02, remoteSessions01, remoteSessions02, 40l); + } + + + @Test + public void testLogoutUser( + @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, + @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, + @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { + + createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics); + + channelStatisticsCrossDc.reset(); + + // Logout single session of user first + UserResource user = ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test"); + UserSessionRepresentation userSession = user.getUserSessions().get(0); + getAdminClient().realm(REALM_NAME).deleteSession(userSession.getId()); + + // Just one session expired. Limit 5 for sent_messages is just if "lastSessionRefresh" periodic thread happened + assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, + sessions01 + SESSIONS_COUNT - 1, sessions02 + SESSIONS_COUNT - 1, remoteSessions01 + SESSIONS_COUNT - 1, remoteSessions02 + SESSIONS_COUNT - 1, 5l); + + // Logout all sessions for user now + user.logout(); + + // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. + assertStatisticsExpected("After user logout", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, + sessions01, sessions02, remoteSessions01, remoteSessions02, 40l); + } + + // AUTH SESSIONS diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java index 8ea51af800..23760a137f 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractSessionCacheCommand.java @@ -17,16 +17,16 @@ package org.keycloak.testsuite.util.cli; -import java.util.function.Function; - 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.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; @@ -318,16 +318,20 @@ public abstract class AbstractSessionCacheCommand extends AbstractCommand { @Override protected void doRunCacheCommand(KeycloakSession session, Cache cache) { String realmName = getArg(1); - String username = getArg(2); - int count = getIntArg(3); - int batchCount = getIntArg(4); + String clientId = getArg(2); + String username = getArg(3); + int count = getIntArg(4); + int batchCount = getIntArg(5); BatchTaskRunner.runInBatches(0, count, batchCount, session.getKeycloakSessionFactory(), (KeycloakSession batchSession, int firstInIteration, int countInIteration) -> { RealmModel realm = batchSession.realms().getRealmByName(realmName); + ClientModel client = realm.getClientByClientId(clientId); UserModel user = batchSession.users().getUserByUsername(username, realm); for (int i=0 ; i "; + return getName() + " "; } }