Merge pull request #4660 from mposolda/crossdc

KEYCLOAK-5618 Fix SessionsPreloadCrossDCTest. Update HOW-TO-RUN docs.…
This commit is contained in:
Marek Posolda 2017-11-09 20:50:38 +01:00 committed by GitHub
commit 39345b0b61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 273 additions and 96 deletions

View file

@ -98,7 +98,9 @@ Infinispan Server setup
<transaction mode="NON_DURABLE_XA" locking="PESSIMISTIC"/> <transaction mode="NON_DURABLE_XA" locking="PESSIMISTIC"/>
<locking acquire-timeout="0" /> <locking acquire-timeout="0" />
<backups> <backups>
<backup site="site2" failure-policy="FAIL" strategy="SYNC" enabled="true"/> <backup site="site2" failure-policy="FAIL" strategy="SYNC" enabled="true">
<take-offline min-wait="60000" after-failures="3" />
</backup>
</backups> </backups>
</replicated-cache-configuration> </replicated-cache-configuration>

View file

@ -117,7 +117,7 @@ But additionally you can enable Kerberos authentication in LDAP provider with th
* KeyTab: $KEYCLOAK_SOURCES/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/http.keytab (Replace $KEYCLOAK_SOURCES with correct absolute path of your sources) * KeyTab: $KEYCLOAK_SOURCES/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/http.keytab (Replace $KEYCLOAK_SOURCES with correct absolute path of your sources)
Once you do this, you should also ensure that your Kerberos client configuration file is properly configured with KEYCLOAK.ORG domain. Once you do this, you should also ensure that your Kerberos client configuration file is properly configured with KEYCLOAK.ORG domain.
See [../testsuite/integration-arquillian/src/test/resources/kerberos/test-krb5.conf](../testsuite/integration-arquillian/src/test/resources/kerberos/test-krb5.conf) for inspiration. The location of Kerberos configuration file See [../testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/test-krb5.conf](../testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/test-krb5.conf) for inspiration. The location of Kerberos configuration file
is platform dependent (In linux it's file `/etc/krb5.conf` ) is platform dependent (In linux it's file `/etc/krb5.conf` )
Then you need to configure your browser to allow SPNEGO/Kerberos login from `localhost` . Then you need to configure your browser to allow SPNEGO/Kerberos login from `localhost` .

View file

@ -22,6 +22,7 @@ import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterListener; import org.keycloak.cluster.ClusterListener;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.cluster.ExecutionResult; import org.keycloak.cluster.ExecutionResult;
import org.keycloak.common.util.Retry;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -140,7 +141,7 @@ public class InfinispanClusterProvider implements ClusterProvider {
private boolean tryLock(String cacheKey, int taskTimeoutInSeconds) { private boolean tryLock(String cacheKey, int taskTimeoutInSeconds) {
LockEntry myLock = createLockEntry(); LockEntry myLock = createLockEntry();
LockEntry existingLock = (LockEntry) crossDCAwareCacheFactory.getCache().putIfAbsent(cacheKey, myLock, taskTimeoutInSeconds, TimeUnit.SECONDS); LockEntry existingLock = InfinispanClusterProviderFactory.putIfAbsentWithRetries(crossDCAwareCacheFactory, cacheKey, myLock, taskTimeoutInSeconds);
if (existingLock != null) { if (existingLock != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef("Task %s in progress already by node %s. Ignoring task.", cacheKey, existingLock.getNode()); logger.tracef("Task %s in progress already by node %s. Ignoring task.", cacheKey, existingLock.getNode());
@ -156,22 +157,15 @@ public class InfinispanClusterProvider implements ClusterProvider {
private void removeFromCache(String cacheKey) { private void removeFromCache(String cacheKey) {
// 3 attempts to send the message (it may fail if some node fails in the meantime) // More attempts to send the message (it may fail if some node fails in the meantime)
int retry = 3; Retry.executeWithBackoff((int iteration) -> {
while (true) {
try { crossDCAwareCacheFactory.getCache().remove(cacheKey);
crossDCAwareCacheFactory.getCache().remove(cacheKey); if (logger.isTraceEnabled()) {
if (logger.isTraceEnabled()) { logger.tracef("Task %s removed from the cache", cacheKey);
logger.tracef("Task %s removed from the cache", cacheKey);
}
return;
} catch (RuntimeException e) {
retry--;
if (retry == 0) {
throw e;
}
} }
}
}, 10, 10);
} }
} }

View file

@ -18,6 +18,7 @@
package org.keycloak.cluster.infinispan; package org.keycloak.cluster.infinispan;
import org.infinispan.Cache; import org.infinispan.Cache;
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.notifications.Listener; import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged; import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
@ -29,6 +30,7 @@ import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.cluster.ClusterProviderFactory; import org.keycloak.cluster.ClusterProviderFactory;
import org.keycloak.common.util.Retry;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -42,6 +44,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -111,7 +115,7 @@ public class InfinispanClusterProviderFactory implements ClusterProviderFactory
// clusterStartTime not yet initialized. Let's try to put our startupTime // clusterStartTime not yet initialized. Let's try to put our startupTime
int serverStartTime = (int) (session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000); int serverStartTime = (int) (session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
existingClusterStartTime = (Integer) crossDCAwareCacheFactory.getCache().putIfAbsent(InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY, serverStartTime); existingClusterStartTime = putIfAbsentWithRetries(crossDCAwareCacheFactory, InfinispanClusterProvider.CLUSTER_STARTUP_TIME_KEY, serverStartTime, -1);
if (existingClusterStartTime == null) { if (existingClusterStartTime == null) {
logger.debugf("Initialized cluster startup time to %s", Time.toDate(serverStartTime).toString()); logger.debugf("Initialized cluster startup time to %s", Time.toDate(serverStartTime).toString());
return serverStartTime; return serverStartTime;
@ -123,6 +127,35 @@ public class InfinispanClusterProviderFactory implements ClusterProviderFactory
} }
// Will retry few times for the case when backup site not available in cross-dc environment.
// The site might be taken offline automatically if "take-offline" properly configured
static <V extends Serializable> V putIfAbsentWithRetries(CrossDCAwareCacheFactory crossDCAwareCacheFactory, String key, V value, int taskTimeoutInSeconds) {
AtomicReference<V> resultRef = new AtomicReference<>();
Retry.executeWithBackoff((int iteration) -> {
try {
V result;
if (taskTimeoutInSeconds > 0) {
result = (V) crossDCAwareCacheFactory.getCache().putIfAbsent(key, value);
} else {
result = (V) crossDCAwareCacheFactory.getCache().putIfAbsent(key, value, taskTimeoutInSeconds, TimeUnit.SECONDS);
}
resultRef.set(result);
} catch (HotRodClientException re) {
logger.warnf(re, "Failed to write key '%s' and value '%s' in iteration '%d' . Retrying", key, value, iteration);
// Rethrow the exception. Retry will take care of handle the exception and eventually retry the operation.
throw re;
}
}, 10, 10);
return resultRef.get();
}
@Override @Override
public void init(Config.Scope config) { public void init(Config.Scope config) {

View file

@ -276,7 +276,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
private void loadSessionsFromRemoteCache(final KeycloakSessionFactory sessionFactory, String cacheName, final int sessionsPerSegment, final int maxErrors) { private void loadSessionsFromRemoteCache(final KeycloakSessionFactory sessionFactory, String cacheName, final int sessionsPerSegment, final int maxErrors) {
log.debugf("Check pre-loading userSessions from remote cache '%s'", cacheName); log.debugf("Check pre-loading sessions from remote cache '%s'", cacheName);
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@ -293,7 +293,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
}); });
log.debugf("Pre-loading userSessions from remote cache '%s' finished", cacheName); log.debugf("Pre-loading sessions from remote cache '%s' finished", cacheName);
} }

View file

@ -18,15 +18,18 @@
package org.keycloak.models.sessions.infinispan.initializer; package org.keycloak.models.sessions.infinispan.initializer;
import org.infinispan.Cache; import org.infinispan.Cache;
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.context.Flag; import org.infinispan.context.Flag;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Retry;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider; import org.keycloak.models.session.UserSessionPersisterProvider;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -101,10 +104,24 @@ public class OfflinePersistentUserSessionLoader implements SessionLoader, Serial
public void afterAllSessionsLoaded(BaseCacheInitializer initializer) { public void afterAllSessionsLoaded(BaseCacheInitializer initializer) {
Cache<String, Serializable> workCache = initializer.getWorkCache(); Cache<String, Serializable> workCache = initializer.getWorkCache();
// Cross-DC aware flag // Will retry few times for the case when backup site not available in cross-dc environment.
workCache // The site might be taken offline automatically if "take-offline" properly configured
.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP) Retry.executeWithBackoff((int iteration) -> {
.put(PERSISTENT_SESSIONS_LOADED, true);
try {
// Cross-DC aware flag
workCache
.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP)
.put(PERSISTENT_SESSIONS_LOADED, true);
} catch (HotRodClientException re) {
log.warnf(re, "Failed to write flag PERSISTENT_SESSIONS_LOADED in iteration '%d' . Retrying", iteration);
// Rethrow the exception. Retry will take care of handle the exception and eventually retry the operation.
throw re;
}
}, 10, 10);
// Just local-DC aware flag // Just local-DC aware flag
workCache workCache

View file

@ -142,7 +142,8 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE) .getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE)
.get(OfflinePersistentUserSessionLoader.PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC); .get(OfflinePersistentUserSessionLoader.PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC);
if (cacheName.equals(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) && sessionsLoaded != null && sessionsLoaded) { if ((cacheName.equals(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) || (cacheName.equals(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME)))
&& sessionsLoaded != null && sessionsLoaded) {
log.debugf("Sessions already loaded in current DC. Skip sessions loading from remote cache '%s'", cacheName); log.debugf("Sessions already loaded in current DC. Skip sessions loading from remote cache '%s'", cacheName);
return true; return true;
} else { } else {

View file

@ -0,0 +1,114 @@
/*
* Copyright 2017 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.cluster.infinispan;
import java.util.concurrent.atomic.AtomicInteger;
import org.infinispan.Cache;
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.context.Flag;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ConcurrencyJDGOfflineBackupsTest {
protected static final Logger logger = Logger.getLogger(ConcurrencyJDGOfflineBackupsTest.class);
public static void main(String[] args) throws Exception {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache1 = createManager(1).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
try {
// Create initial item
UserSessionEntity session = new UserSessionEntity();
session.setId("123");
session.setRealmId("foo");
session.setBrokerSessionId("!23123123");
session.setBrokerUserId(null);
session.setUser("foo");
session.setLoginUsername("foo");
session.setIpAddress("123.44.143.178");
session.setStarted(Time.currentTime());
session.setLastSessionRefresh(Time.currentTime());
// AuthenticatedClientSessionEntity clientSession = new AuthenticatedClientSessionEntity();
// clientSession.setAuthMethod("saml");
// clientSession.setAction("something");
// clientSession.setTimestamp(1234);
// clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2")));
// clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2")));
// session.getAuthenticatedClientSessions().put(CLIENT_1_UUID.toString(), clientSession.getId());
SessionEntityWrapper<UserSessionEntity> wrappedSession = new SessionEntityWrapper<>(session);
// Some dummy testing of remoteStore behaviour
logger.info("Before put");
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger errorsCount = new AtomicInteger(0);
for (int i=0 ; i<100 ; i++) {
try {
cache1
.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL) // will still invoke remoteStore . Just doesn't propagate to cluster
.put("123", wrappedSession);
successCount.incrementAndGet();
Thread.sleep(1000);
logger.infof("Success in the iteration: %d", i);
} catch (HotRodClientException hrce) {
logger.errorf("Failed to put the item in the iteration: %d ", i);
errorsCount.incrementAndGet();
}
}
logger.infof("SuccessCount: %d, ErrorsCount: %d", successCount.get(), errorsCount.get());
// logger.info("After put");
//
// cache1.replace("123", wrappedSession);
//
// logger.info("After replace");
//
// cache1.get("123");
//
// logger.info("After cache1.get");
// cache2.get("123");
//
// logger.info("After cache2.get");
} finally {
// Finish JVM
cache1.getCacheManager().stop();
}
}
private static EmbeddedCacheManager createManager(int threadId) {
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class);
}
}

View file

@ -458,114 +458,115 @@ The cross DC requires setting a profile specifying used cache server by specifyi
#### Run Cross-DC Tests from Maven #### Run Cross-DC Tests from Maven
a) First compile the Infinispan/JDG test server via the following command: a) Prepare the environment. Compile the infinispan server and eventually Keycloak on JBoss server.
a1) If you want to use **Undertow** based Keycloak container, you just need to download and prepare the
Infinispan/JDG test server via the following command:
`mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -f testsuite/integration-arquillian -DskipTests clean install` `mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -f testsuite/integration-arquillian -DskipTests clean install`
or *note: 'cache-server-infinispan' can be replaced by 'cache-server-jdg'*
`mvn -Pcache-server-jdg,auth-servers-crossdc-undertow -f testsuite/integration-arquillian -DskipTests clean install` a2) If you want to use **JBoss-based** Keycloak backend containers instead of containers on Embedded Undertow,
you need to prepare both the Infinispan/JDG test server and the Keycloak server on Wildfly/EAP. Run following command:
b) Then in case you want to use **JBoss-based** Keycloak backend containers instead of containers on Embedded Undertow run following command: `mvn -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -f testsuite/integration-arquillian -DskipTests clean install`
`mvn -Pauth-servers-crossdc-jboss,auth-server-wildfly -f testsuite/integration-arquillian -DskipTests clean install` *note: 'cache-server-infinispan' can be replaced by 'cache-server-jdg'*
*note: 'auth-server-wildfly' can be replaced by 'auth-server-eap'* *note: 'auth-server-wildfly' can be replaced by 'auth-server-eap'*
By default JBoss-based containers use in-memory h2 database. It can be configured to use real DB, e.g. with following command: By default JBoss-based containers use TCP-based h2 database. It can be configured to use real DB, e.g. with following command:
`mvn -Pauth-servers-crossdc-jboss,auth-server-wildfly,jpa -f testsuite/integration-arquillian -DskipTests clean install -Djdbc.mvn.groupId=org.mariadb.jdbc -Djdbc.mvn.artifactId=mariadb-java-client -Djdbc.mvn.version=2.0.3 -Dkeycloak.connectionsJpa.url=jdbc:mariadb://localhost:3306/keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsJpa.user=keycloak` `mvn -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly,jpa -f testsuite/integration-arquillian -DskipTests clean install -Djdbc.mvn.groupId=org.mariadb.jdbc -Djdbc.mvn.artifactId=mariadb-java-client -Djdbc.mvn.version=2.0.3 -Dkeycloak.connectionsJpa.url=jdbc:mariadb://localhost:3306/keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsJpa.user=keycloak`
c1) Then you can run the tests using the following command (adjust the test specification according to your needs) for Keycloak backend containers on **Undertow**: b1) For **Undertow** Keycloak backend containers, you can run the tests using the following command (adjust the test specification according to your needs):
`mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install` `mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
or *note: 'cache-server-infinispan' can be replaced by 'cache-server-jdg'*
`mvn -Pcache-server-jdg,auth-servers-crossdc-undertow -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install` *note: It can be useful to add additional system property to enable logging:*
`-Dkeycloak.infinispan.logging.level=debug`
c2) For **JBoss-based** Keycloak backend containers: b2) For **JBoss-based** Keycloak backend containers, you can run the tests like this:
`mvn -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install` `mvn -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
or *note: 'cache-server-infinispan' can be replaced by 'cache-server-jdg'*
`mvn -Pcache-server-jdg,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base clean install`
*note: 'auth-server-wildfly can be replaced by auth-server-eap'* *note: 'auth-server-wildfly can be replaced by auth-server-eap'*
**note** **note**:
Previous commands can be "squashed" into one. E.g.: For **JBoss-based** Keycloak backend containers on real DB, the previous commands from (a2) and (b2) can be "squashed" into one. E.g.:
`mvn -f testsuite/integration-arquillian clean install -Dtest=*.crossdc.* -Djdbc.mvn.groupId=org.mariadb.jdbc -Djdbc.mvn.artifactId=mariadb-java-client -Djdbc.mvn.version=2.0.3 -Dkeycloak.connectionsJpa.url=jdbc:mariadb://localhost:3306/keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsJpa.user=keycloak -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly,jpa clean install` `mvn -f testsuite/integration-arquillian clean install -Dtest=*.crossdc.* -Djdbc.mvn.groupId=org.mariadb.jdbc -Djdbc.mvn.artifactId=mariadb-java-client -Djdbc.mvn.version=2.0.3 -Dkeycloak.connectionsJpa.url=jdbc:mariadb://localhost:3306/keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsJpa.user=keycloak -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly,jpa clean install`
It can be useful to add additional system property to enable logging:
-Dkeycloak.infinispan.logging.level=debug
**Tests from package "manual"** uses manual lifecycle for all servers, so needs to be executed manually. Also needs to be executed with real DB like MySQL. You can run them with: #### Run "Manual" Cross-DC Tests from Maven
Tests from package "manual" uses manual lifecycle for all servers, so needs to be executed manually.
mvn -Pcache-server-infinispan -Dtest=*.crossdc.manual.* -Dmanual.mode=true \ First prepare the environment and do the step (a) from previous paragraph.
-Dkeycloak.connectionsJpa.url.crossdc=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver.crossdc=com.mysql.jdbc.Driver \
-Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak \
-pl testsuite/integration-arquillian/tests/base test
c1) For **Undertow** Keycloak backend containers, you can run the test using following command:
`mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -Dtest=*.crossdc.manual.* -Dmanual.mode=true -Drun.h2=true -Dkeycloak.connectionsJpa.url.crossdc="jdbc:h2:tcp://localhost:9092/mem:keycloak-dc-shared;DB_CLOSE_DELAY=-1" -pl testsuite/integration-arquillian/tests/base clean install`
*note: As you can see, there is a need to run TCP-Based H2 for this test. In-memory H2 won't work due the data need
to persist the stop of all the Keycloak servers.*
If you want to test with real DB like MySQL, you can run them with:
`mvn -Pcache-server-infinispan,auth-servers-crossdc-undertow -Dtest=*.crossdc.manual.* -Dmanual.mode=true -Dkeycloak.connectionsJpa.url.crossdc=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver.crossdc=com.mysql.jdbc.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak -pl testsuite/integration-arquillian/tests/base clean install`
c2) For **JBoss-based** Keycloak backend containers, you can run the tests like this:
`mvn -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.manual.* -Dmanual.mode=true -pl testsuite/integration-arquillian/tests/base clean install`
*note: TCP-based H2 is used by default when running cross-dc tests on JBoss-based Keycloak container.
So no need to explicitly specify it like in (c1) for undertow.*
#### Run Cross-DC Tests from Intellij IDEA #### Run Cross-DC Tests from Intellij IDEA
First we will manually download, configure and run infinispan server. Then we can run the tests from IDE against 1 server. It's more effective during First we will manually download, configure and run infinispan servers. Then we can run the tests from IDE against the servers.
development as there is no need to restart infinispan server(s) among test runs. It's more effective during development as there is no need to restart infinispan server(s) among test runs.
1) Download infinispan server 8.2.X from http://infinispan.org/download/ 1) Download infinispan server 8.2.X from http://infinispan.org/download/ and go through the steps
from the [../../misc/CrossDataCenter.md](../../misc/CrossDataCenter.md) and the `Infinispan Server Setup` part.
2) Edit `ISPN_SERVER_HOME/standalone/configuration/standalone.xml` and add these local-caches to the section under cache-container `local` : Assume you have both Infinispan/JDG servers up and running.
<cache-container name="local" ... **TODO:** Change this once CrossDataCenter.md is removed and converted to the proper docs.
...
<local-cache-configuration name="sessions-cfg" start="EAGER" batching="false">
<transaction mode="NON_XA" locking="PESSIMISTIC"/>
</local-cache-configuration>
<local-cache name="sessions" configuration="sessions-cfg" /> 2) Setup MySQL database or some other shared database.
<local-cache name="offlineSessions" configuration="sessions-cfg" />
<local-cache name="loginFailures" configuration="sessions-cfg" />
<local-cache name="actionTokens" configuration="sessions-cfg" />
<local-cache name="work" configuration="sessions-cfg" />
</cache>
3) Run the server through `./standalone.sh`
4) Setup MySQL database or some other shared database.
5) Ensure that org.wildfly.arquillian:wildfly-arquillian-container-managed is on the classpath when running test. On Intellij, it can be 3) Ensure that `org.wildfly.arquillian:wildfly-arquillian-container-managed` is on the classpath when running test. On Intellij, it can be
done by going to: View -> Tool Windows -> Maven projects. Then check profile "cache-server-infinispan". The tests will use this profile when executed. done by going to: `View` -> `Tool Windows` -> `Maven projects`. Then check profile `cache-server-infinispan` and `auth-servers-crossdc-undertow`.
The tests will use this profile when executed.
6) Run the LoginCrossDCTest (or any other test) with those properties. In shortcut, it's using MySQL database, disabled L1 lifespan and 4) Run the LoginCrossDCTest (or any other test) with those properties. In shortcut, it's using MySQL database and
connects to the remoteStore provided by infinispan server configured in previous steps: connects to the remoteStore provided by infinispan server configured in previous steps:
-Dauth.server.crossdc=true -Dauth.server.undertow.crossdc=true -Dcache.server.lifecycle.skip=true -Dkeycloak.connectionsJpa.url.crossdc=jdbc:mysql://localhost/keycloak `-Dauth.server.crossdc=true -Dauth.server.undertow.crossdc=true -Dcache.server.lifecycle.skip=true -Dkeycloak.connectionsInfinispan.clustered=true -Dkeycloak.connectionsJpa.url.crossdc=jdbc:mysql://localhost/keycloak -Dkeycloak.connectionsJpa.driver.crossdc=com.mysql.jdbc.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsInfinispan.clustered=true -Dkeycloak.connectionsInfinispan.remoteStorePort=12232 -Dkeycloak.connectionsInfinispan.remoteStorePort.2=13232 -Dkeycloak.connectionsInfinispan.sessionsOwners=1 -Dsession.cache.owners=1 -Dkeycloak.infinispan.logging.level=debug -Dresources`
-Dkeycloak.connectionsJpa.driver.crossdc=com.mysql.jdbc.Driver -Dkeycloak.connectionsJpa.user=keycloak
-Dkeycloak.connectionsJpa.password=keycloak -Dkeycloak.connectionsInfinispan.clustered=true -Dkeycloak.connectionsInfinispan.l1Lifespan=0
-Dkeycloak.connectionsInfinispan.remoteStorePort=11222 -Dkeycloak.connectionsInfinispan.remoteStorePort.2=11222 -Dkeycloak.connectionsInfinispan.sessionsOwners=1
-Dsession.cache.owners=1 -Dkeycloak.infinispan.logging.level=debug -Dresources
NOTE: Tests from package "manual" (eg. SessionsPreloadCrossDCTest) needs to be executed with managed containers. **NOTE**: Tests from package `manual` (eg. SessionsPreloadCrossDCTest) needs to be executed with managed containers.
So skip steps 1,2 and add property `-Dmanual.mode=true` and change "cache.server.lifecycle.skip" to false `-Dcache.server.lifecycle.skip=false` or remove it. So skip steps 1,2 and add property `-Dmanual.mode=true` and change "cache.server.lifecycle.skip" to false `-Dcache.server.lifecycle.skip=false` or remove it.
7) If you want to debug and test manually, the servers are running on these ports (Note that not all backend servers are running by default and some might be also unused by loadbalancer): 5) If you want to debug or test manually, the servers are running on these ports (Note that not all backend servers are running by default and some might be also unused by loadbalancer):
Loadbalancer -> "http://localhost:8180/auth" * *Loadbalancer* -> "http://localhost:8180/auth"
auth-server-undertow-cross-dc-0_1 -> "http://localhost:8101/auth"
auth-server-undertow-cross-dc-0_2-manual -> "http://localhost:8102/auth" * *auth-server-undertow-cross-dc-0_1* -> "http://localhost:8101/auth"
auth-server-undertow-cross-dc-1_1 -> "http://localhost:8111/auth"
auth-server-undertow-cross-dc-1_2-manual -> "http://localhost:8112/auth" * *auth-server-undertow-cross-dc-0_2-manual* -> "http://localhost:8102/auth"
* *auth-server-undertow-cross-dc-1_1* -> "http://localhost:8111/auth"
* *auth-server-undertow-cross-dc-1_2-manual* -> "http://localhost:8112/auth"
## Run Docker Authentication test ## Run Docker Authentication test

View file

@ -39,7 +39,9 @@
<transaction mode="NON_DURABLE_XA" locking="PESSIMISTIC"/> <transaction mode="NON_DURABLE_XA" locking="PESSIMISTIC"/>
<locking acquire-timeout="0" /> <locking acquire-timeout="0" />
<backups> <backups>
<backup site="{$remote.site}" failure-policy="FAIL" strategy="SYNC" enabled="true"/> <backup site="{$remote.site}" failure-policy="FAIL" strategy="SYNC" enabled="true">
<take-offline min-wait="60000" after-failures="3" />
</backup>
</backups> </backups>
</replicated-cache-configuration> </replicated-cache-configuration>

View file

@ -52,8 +52,9 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
stopAllCacheServersAndAuthServers(); stopAllCacheServersAndAuthServers();
// Start DC1 only // Start DC1 and only the cache container from DC2. All Keycloak nodes on DC2 are stopped
containerController.start(getCacheServer(DC.FIRST).getQualifier()); containerController.start(getCacheServer(DC.FIRST).getQualifier());
containerController.start(getCacheServer(DC.SECOND).getQualifier());
startBackendNode(DC.FIRST, 0); startBackendNode(DC.FIRST, 0);
enableLoadBalancerNode(DC.FIRST, 0); enableLoadBalancerNode(DC.FIRST, 0);
@ -119,7 +120,6 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
List<OAuthClient.AccessTokenResponse> tokenResponses = createInitialSessions(false); List<OAuthClient.AccessTokenResponse> tokenResponses = createInitialSessions(false);
// Start 2nd DC. // Start 2nd DC.
containerController.start(getCacheServer(DC.SECOND).getQualifier());
startBackendNode(DC.SECOND, 0); startBackendNode(DC.SECOND, 0);
enableLoadBalancerNode(DC.SECOND, 0); enableLoadBalancerNode(DC.SECOND, 0);
@ -130,7 +130,7 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
Assert.assertEquals(sessions01, sessionsBefore + SESSIONS_COUNT); Assert.assertEquals(sessions01, sessionsBefore + SESSIONS_COUNT);
Assert.assertEquals(sessions02, sessionsBefore + SESSIONS_COUNT); Assert.assertEquals(sessions02, sessionsBefore + SESSIONS_COUNT);
// On DC2 sessions were preloaded from from remoteCache // On DC2 sessions were preloaded from remoteCache
Assert.assertTrue(getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.WORK_CACHE_NAME).contains("distributed::remoteCacheLoad::sessions")); Assert.assertTrue(getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.WORK_CACHE_NAME).contains("distributed::remoteCacheLoad::sessions"));
// Assert refreshing works // Assert refreshing works
@ -157,13 +157,15 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
// Stop Everything // Stop Everything
stopAllCacheServersAndAuthServers(); stopAllCacheServersAndAuthServers();
// Start DC1. Sessions should be preloaded from DB // Start cache containers on both DC1 and DC2
containerController.start(getCacheServer(DC.FIRST).getQualifier()); containerController.start(getCacheServer(DC.FIRST).getQualifier());
containerController.start(getCacheServer(DC.SECOND).getQualifier());
// Start Keycloak on DC1. Sessions should be preloaded from DB
startBackendNode(DC.FIRST, 0); startBackendNode(DC.FIRST, 0);
enableLoadBalancerNode(DC.FIRST, 0); enableLoadBalancerNode(DC.FIRST, 0);
// Start DC2. Sessions should be preloaded from remoteCache // Start Keycloak on DC2. Sessions should be preloaded from remoteCache
containerController.start(getCacheServer(DC.SECOND).getQualifier());
startBackendNode(DC.SECOND, 0); startBackendNode(DC.SECOND, 0);
enableLoadBalancerNode(DC.SECOND, 0); enableLoadBalancerNode(DC.SECOND, 0);
@ -210,7 +212,6 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
} }
// Start 2nd DC. // Start 2nd DC.
containerController.start(getCacheServer(DC.SECOND).getQualifier());
startBackendNode(DC.SECOND, 0); startBackendNode(DC.SECOND, 0);
enableLoadBalancerNode(DC.SECOND, 0); enableLoadBalancerNode(DC.SECOND, 0);

View file

@ -82,5 +82,17 @@ if [ $1 == "crossdc" ]; then
cd tests/base cd tests/base
mvn clean test -B -nsu -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.**.* 2>&1 | mvn clean test -B -nsu -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.**.* 2>&1 |
java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
exit ${PIPESTATUS[0]} BASE_TESTS_STATUS=${PIPESTATUS[0]}
mvn clean test -B -nsu -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly -Dtest=*.crossdc.manual.* -Dmanual.mode=true 2>&1 |
java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
MANUAL_TESTS_STATUS=${PIPESTATUS[0]}
echo "BASE_TESTS_STATUS=$BASE_TESTS_STATUS, MANUAL_TESTS_STATUS=$MANUAL_TESTS_STATUS";
if [ $BASE_TESTS_STATUS -eq 0 -a $MANUAL_TESTS_STATUS -eq 0 ]; then
exit 0;
else
exit 1;
fi;
fi fi