Merge pull request #4212 from hmlnarik/KEYCLOAK-4189-Cross-DC-testing
KEYCLOAK-4189 - Cross DC testing
This commit is contained in:
commit
bc407a7968
45 changed files with 1752 additions and 158 deletions
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.keycloak.common.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
|
@ -24,9 +26,21 @@ import java.util.Properties;
|
|||
*/
|
||||
public class SystemEnvProperties extends Properties {
|
||||
|
||||
private final Map<String, String> overrides;
|
||||
|
||||
public SystemEnvProperties(Map<String, String> overrides) {
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
||||
public SystemEnvProperties() {
|
||||
this.overrides = Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProperty(String key) {
|
||||
if (key.startsWith("env.")) {
|
||||
if (overrides.containsKey(key)) {
|
||||
return overrides.get(key);
|
||||
} else if (key.startsWith("env.")) {
|
||||
return System.getenv().get(key.substring(4));
|
||||
} else {
|
||||
return System.getProperty(key);
|
||||
|
|
|
@ -3,6 +3,8 @@ Test Cross-Data-Center scenario (test with external JDG server)
|
|||
|
||||
These are temporary notes. This docs should be removed once we have cross-DC support finished and properly documented.
|
||||
|
||||
Note that these steps are already automated, see Cross-DC tests section in [HOW-TO-RUN.md](../testsuite/integration-arquillian/HOW-TO-RUN.md) document.
|
||||
|
||||
What is working right now is:
|
||||
- Propagating of invalidation messages for "realms" and "users" caches
|
||||
- All the other things provided by ClusterProvider, which is:
|
||||
|
|
|
@ -154,7 +154,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
|
||||
if (clustered) {
|
||||
String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
|
||||
configureTransport(gcb, nodeName);
|
||||
String jgroupsUdpMcastAddr = config.get("jgroupsUdpMcastAddr", System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR));
|
||||
configureTransport(gcb, nodeName, jgroupsUdpMcastAddr);
|
||||
}
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
|
@ -317,12 +318,21 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
return cb.build();
|
||||
}
|
||||
|
||||
protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
|
||||
private static final Object CHANNEL_INIT_SYNCHRONIZER = new Object();
|
||||
|
||||
protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName, String jgroupsUdpMcastAddr) {
|
||||
if (nodeName == null) {
|
||||
gcb.transport().defaultTransport();
|
||||
} else {
|
||||
FileLookup fileLookup = FileLookupFactory.newInstance();
|
||||
|
||||
synchronized (CHANNEL_INIT_SYNCHRONIZER) {
|
||||
String originalMcastAddr = System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
|
||||
if (jgroupsUdpMcastAddr == null) {
|
||||
System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
|
||||
} else {
|
||||
System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, jgroupsUdpMcastAddr);
|
||||
}
|
||||
try {
|
||||
// Compatibility with Wildfly
|
||||
JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
|
||||
|
@ -335,6 +345,13 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (originalMcastAddr == null) {
|
||||
System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
|
||||
} else {
|
||||
System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, originalMcastAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
|||
|
||||
// System property used on Wildfly to identify distributedCache address and sticky session route
|
||||
String JBOSS_NODE_NAME = "jboss.node.name";
|
||||
String JGROUPS_UDP_MCAST_ADDR = "jgroups.udp.mcast_addr";
|
||||
|
||||
|
||||
<K, V> Cache<K, V> getCache(String name);
|
||||
|
|
|
@ -220,7 +220,7 @@ public abstract class CacheManager {
|
|||
|
||||
addInvalidationsFromEvent(event, invalidations);
|
||||
|
||||
getLogger().debugf("Invalidating %d cache items after received event %s", invalidations.size(), event);
|
||||
getLogger().debugf("[%s] Invalidating %d cache items after received event %s", cache.getCacheManager().getAddress(), invalidations.size(), event);
|
||||
|
||||
for (String invalidation : invalidations) {
|
||||
invalidateObject(invalidation);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.cache.infinispan;
|
||||
package org.keycloak.models.cache.infinispan.events;
|
||||
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
|
|
@ -14,7 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.cache.infinispan;
|
||||
package org.keycloak.models.cache.infinispan.events;
|
||||
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
|
|
@ -19,8 +19,8 @@ package org.keycloak.models.sessions.infinispan;
|
|||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.*;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
|
||||
import java.util.*;
|
||||
|
@ -58,7 +58,12 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
|
|||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
|
||||
AddInvalidatedActionTokenEvent event = new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue);
|
||||
this.tx.notify(cluster, generateActionTokenEventId(), event, false);
|
||||
}
|
||||
|
||||
private static String generateActionTokenEventId() {
|
||||
return InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS + "/" + UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -93,6 +98,6 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
|
|||
}
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
|
||||
this.tx.notify(cluster, generateActionTokenEventId(), new RemoveActionTokensSpecificEvent(userId, actionId), false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,16 @@ import org.keycloak.common.util.Time;
|
|||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.*;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.infinispan.remoting.transport.Address;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -38,6 +40,10 @@ import org.infinispan.context.Flag;
|
|||
*/
|
||||
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class);
|
||||
|
||||
private volatile Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache;
|
||||
|
||||
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
|
||||
|
||||
/**
|
||||
|
@ -49,34 +55,7 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
|
|||
|
||||
@Override
|
||||
public ActionTokenStoreProvider create(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
|
||||
cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
|
||||
if (event instanceof RemoveActionTokensSpecificEvent) {
|
||||
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
|
||||
|
||||
actionTokenCache
|
||||
.getAdvancedCache()
|
||||
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
|
||||
.forEach(actionTokenCache::remove);
|
||||
} else if (event instanceof AddInvalidatedActionTokenEvent) {
|
||||
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
|
||||
|
||||
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
|
||||
actionTokenCache.put(e.getKey(), e.getTokenValue());
|
||||
} else {
|
||||
actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
|
||||
return new InfinispanActionTokenStoreProvider(session, this.actionTokenCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,8 +63,57 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
|
|||
this.config = config;
|
||||
}
|
||||
|
||||
private static Cache<ActionTokenReducedKey, ActionTokenValueEntity> initActionTokenCache(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
|
||||
final Address cacheAddress = cache.getCacheManager().getAddress();
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
|
||||
cluster.registerListener(ClusterProvider.ALL, event -> {
|
||||
if (event instanceof RemoveActionTokensSpecificEvent) {
|
||||
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
|
||||
|
||||
LOG.debugf("[%s] Removing token invalidation for user+action: userId=%s, actionId=%s", cacheAddress, e.getUserId(), e.getActionId());
|
||||
|
||||
cache
|
||||
.getAdvancedCache()
|
||||
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
|
||||
.forEach(cache::remove);
|
||||
} else if (event instanceof AddInvalidatedActionTokenEvent) {
|
||||
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
|
||||
|
||||
LOG.debugf("[%s] Invalidating token %s", cacheAddress, e.getKey());
|
||||
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
|
||||
cache.put(e.getKey(), e.getTokenValue());
|
||||
} else {
|
||||
cache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LOG.debugf("[%s] Registered cluster listeners", cacheAddress);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = this.actionTokenCache;
|
||||
|
||||
// It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
|
||||
// means also listeners will start only after first cache initialization - that would be too late
|
||||
if (cache == null) {
|
||||
synchronized (this) {
|
||||
cache = this.actionTokenCache;
|
||||
if (cache == null) {
|
||||
this.actionTokenCache = initActionTokenCache(factory.create());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -92,7 +92,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
|
|||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
|
||||
|
||||
log.debug("Registered cluster listeners");
|
||||
log.debugf("[%s] Registered cluster listeners", authSessionsCache.getCacheManager().getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,11 @@ public class ActionTokenReducedKey implements Serializable {
|
|||
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "userId=" + userId + ", actionId=" + actionId + ", actionVerificationNonce=" + actionVerificationNonce;
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,7 +53,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
|
|||
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
|
||||
output.writeByte(VERSION_1);
|
||||
|
||||
output.writeBoolean(! t.notes.isEmpty());
|
||||
output.writeBoolean(t.notes.isEmpty());
|
||||
if (! t.notes.isEmpty()) {
|
||||
output.writeObject(t.notes);
|
||||
}
|
||||
|
|
|
@ -70,8 +70,10 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
@ -88,6 +90,8 @@ public class KeycloakApplication extends Application {
|
|||
|
||||
public static final String KEYCLOAK_EMBEDDED = "keycloak.embedded";
|
||||
|
||||
public static final String SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES = "keycloak.server.context.config.property-overrides";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
|
||||
|
||||
protected boolean embedded = false;
|
||||
|
@ -287,7 +291,13 @@ public class KeycloakApplication extends Application {
|
|||
}
|
||||
|
||||
if (node != null) {
|
||||
Properties properties = new SystemEnvProperties();
|
||||
Map<String, String> propertyOverridesMap = new HashMap<>();
|
||||
String propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES);
|
||||
if (context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES) != null) {
|
||||
JsonNode jsonObj = new ObjectMapper().readTree(propertyOverrides);
|
||||
jsonObj.fields().forEachRemaining(e -> propertyOverridesMap.put(e.getKey(), e.getValue().asText()));
|
||||
}
|
||||
Properties properties = new SystemEnvProperties(propertyOverridesMap);
|
||||
Config.init(new JsonConfigProvider(node, properties));
|
||||
} else {
|
||||
throw new RuntimeException("Keycloak config not found.");
|
||||
|
|
|
@ -417,4 +417,22 @@ and argument: `-p 8181`
|
|||
|
||||
3) Run loadbalancer (class `SimpleUndertowLoadBalancer`) without arguments and system properties. Loadbalancer runs on port 8180, so you can access Keycloak on `http://localhost:8180/auth`
|
||||
|
||||
## Cross-DC tests
|
||||
|
||||
Cross-DC tests use 2 data centers, each with one automatically started and one manually controlled backend servers
|
||||
(currently only Keycloak on Undertow), and 1 frontend loadbalancer server node that sits in front of all servers.
|
||||
The browser usually communicates directly with the frontent node and the test controls where the HTTP requests
|
||||
land by adjusting load balancer configuration (e.g. to direct the traffic to only a single DC).
|
||||
|
||||
For an example of a test, see [org.keycloak.testsuite.crossdc.ActionTokenCrossDCTest](tests/base/src/test/java/org/keycloak/testsuite/crossdc/ActionTokenCrossDCTest.java).
|
||||
|
||||
The cross DC requires setting a profile specifying used cache server (currently only Infinispan) by specifying
|
||||
`cache-server-infinispan` profile in maven.
|
||||
|
||||
#### Run Cross-DC Tests from Maven
|
||||
|
||||
Run the following command (adjust the test specification according to your needs):
|
||||
|
||||
`mvn -Pcache-server-infinispan -Dtest=*.crossdc.* -pl testsuite/integration-arquillian/tests/base test`
|
||||
|
||||
_Someone using IntelliJ IDEA, please describe steps for that IDE_
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
<arquillian-drone.version>2.0.1.Final</arquillian-drone.version>
|
||||
<arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
|
||||
<arquillian-wildfly-container.version>2.1.0.Alpha2</arquillian-wildfly-container.version>
|
||||
<arquillian-infinispan-container.version>1.2.0.Beta2</arquillian-infinispan-container.version>
|
||||
<version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
|
||||
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
|
||||
|
||||
|
@ -87,6 +88,11 @@
|
|||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan.arquillian.container</groupId>
|
||||
<artifactId>infinispan-arquillian-impl</artifactId>
|
||||
<version>${arquillian-infinispan-container.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wildfly.arquillian</groupId>
|
||||
<artifactId>wildfly-arquillian-container-managed</artifactId>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.testsuite.arquillian;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public interface LoadBalancerController {
|
||||
|
||||
void enableAllBackendNodes();
|
||||
|
||||
void disableAllBackendNodes();
|
||||
|
||||
void enableBackendNodeByName(String nodeName);
|
||||
|
||||
void disableBackendNodeByName(String nodeName);
|
||||
|
||||
}
|
|
@ -70,6 +70,11 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-testsuite-providers</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
||||
|
|
|
@ -48,6 +48,8 @@ import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
|||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import java.io.IOException;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
|
@ -55,6 +57,7 @@ import java.lang.reflect.Field;
|
|||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> {
|
||||
|
||||
|
@ -75,6 +78,14 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
|
|||
di.setContextPath("/auth");
|
||||
di.setDeploymentName("Keycloak");
|
||||
di.addInitParameter(KeycloakApplication.KEYCLOAK_EMBEDDED, "true");
|
||||
if (configuration.getKeycloakConfigPropertyOverridesMap() != null) {
|
||||
try {
|
||||
di.addInitParameter(KeycloakApplication.SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES,
|
||||
JsonSerialization.writeValueAsString(configuration.getKeycloakConfigPropertyOverridesMap()));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
di.setDefaultServletConfig(new DefaultServletConfig(true));
|
||||
di.addWelcomePage("theme/keycloak/welcome/resources/index.html");
|
||||
|
@ -176,8 +187,6 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
|
|||
log.info("Using route: " + configuration.getRoute());
|
||||
}
|
||||
|
||||
SetSystemProperty setRouteProperty = new SetSystemProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME, configuration.getRoute());
|
||||
try {
|
||||
DeploymentInfo di = createAuthServerDeploymentInfo();
|
||||
undertow.deploy(di);
|
||||
ResteasyDeployment deployment = (ResteasyDeployment) di.getServletContextAttributes().get(ResteasyDeployment.class.getName());
|
||||
|
@ -186,9 +195,6 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
|
|||
setupDevConfig();
|
||||
|
||||
log.info("Auth server started in " + (System.currentTimeMillis() - start) + " ms\n");
|
||||
} finally {
|
||||
setRouteProperty.revert();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
|
||||
package org.keycloak.testsuite.arquillian.undertow;
|
||||
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.arquillian.undertow.UndertowContainerConfiguration;
|
||||
import org.jboss.arquillian.container.spi.ConfigurationException;
|
||||
import org.jboss.logging.Logger;
|
||||
|
@ -29,6 +34,8 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
|
|||
private String resourcesHome;
|
||||
private boolean remoteMode;
|
||||
private String route;
|
||||
private String keycloakConfigPropertyOverrides;
|
||||
private Map<String, String> keycloakConfigPropertyOverridesMap;
|
||||
|
||||
private int bindHttpPortOffset = 0;
|
||||
|
||||
|
@ -72,6 +79,18 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
|
|||
this.remoteMode = remoteMode;
|
||||
}
|
||||
|
||||
public String getKeycloakConfigPropertyOverrides() {
|
||||
return keycloakConfigPropertyOverrides;
|
||||
}
|
||||
|
||||
public void setKeycloakConfigPropertyOverrides(String keycloakConfigPropertyOverrides) {
|
||||
this.keycloakConfigPropertyOverrides = keycloakConfigPropertyOverrides;
|
||||
}
|
||||
|
||||
public Map<String, String> getKeycloakConfigPropertyOverridesMap() {
|
||||
return keycloakConfigPropertyOverridesMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws ConfigurationException {
|
||||
super.validate();
|
||||
|
@ -81,6 +100,15 @@ public class KeycloakOnUndertowConfiguration extends UndertowContainerConfigurat
|
|||
setBindHttpPort(newPort);
|
||||
log.info("KeycloakOnUndertow will listen on port: " + newPort);
|
||||
|
||||
if (this.keycloakConfigPropertyOverrides != null) {
|
||||
try {
|
||||
TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
|
||||
this.keycloakConfigPropertyOverridesMap = JsonSerialization.sysPropertiesAwareMapper.readValue(this.keycloakConfigPropertyOverrides, typeRef);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO validate workerThreads
|
||||
|
||||
}
|
||||
|
|
|
@ -17,13 +17,15 @@
|
|||
|
||||
package org.keycloak.testsuite.arquillian.undertow;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
class SetSystemProperty {
|
||||
class SetSystemProperty implements Closeable {
|
||||
|
||||
private String name;
|
||||
private String oldValue;
|
||||
private final String name;
|
||||
private final String oldValue;
|
||||
|
||||
public SetSystemProperty(String name, String value) {
|
||||
this.name = name;
|
||||
|
@ -50,4 +52,9 @@ class SetSystemProperty {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
revert();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package org.keycloak.testsuite.arquillian.undertow.lb;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -36,6 +35,8 @@ import io.undertow.util.AttachmentKey;
|
|||
import io.undertow.util.Headers;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Loadbalancer on embedded undertow. Supports sticky session over "AUTH_SESSION_ID" cookie and failover to different node when sticky node not available.
|
||||
|
@ -53,8 +54,9 @@ public class SimpleUndertowLoadBalancer {
|
|||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final String nodesString;
|
||||
private final Map<String, URI> backendNodes;
|
||||
private Undertow undertow;
|
||||
private LoadBalancingProxyClient lb;
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
@ -77,15 +79,14 @@ public class SimpleUndertowLoadBalancer {
|
|||
public SimpleUndertowLoadBalancer(String host, int port, String nodesString) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.nodesString = nodesString;
|
||||
log.infof("Keycloak nodes: %s", nodesString);
|
||||
this.backendNodes = parseNodes(nodesString);
|
||||
log.infof("Keycloak nodes: %s", backendNodes);
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
Map<String, String> nodes = parseNodes(nodesString);
|
||||
try {
|
||||
HttpHandler proxyHandler = createHandler(nodes);
|
||||
HttpHandler proxyHandler = createHandler();
|
||||
|
||||
undertow = Undertow.builder()
|
||||
.addHttpListener(port, host)
|
||||
|
@ -104,24 +105,51 @@ public class SimpleUndertowLoadBalancer {
|
|||
undertow.stop();
|
||||
}
|
||||
|
||||
|
||||
static Map<String, String> parseNodes(String nodes) {
|
||||
String[] nodesArray = nodes.split(",");
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
for (String nodeStr : nodesArray) {
|
||||
String[] node = nodeStr.trim().split("=");
|
||||
if (node.length != 2) {
|
||||
throw new IllegalArgumentException("Illegal node format in the configuration: " + nodeStr);
|
||||
public void enableAllBackendNodes() {
|
||||
backendNodes.forEach((route, uri) -> {
|
||||
lb.removeHost(uri);
|
||||
lb.addHost(uri, route);
|
||||
});
|
||||
}
|
||||
result.put(node[0].trim(), node[1].trim());
|
||||
|
||||
public void disableAllBackendNodes() {
|
||||
log.debugf("Load balancer: disabling all nodes");
|
||||
backendNodes.values().forEach(lb::removeHost);
|
||||
}
|
||||
|
||||
public void enableBackendNodeByName(String nodeName) {
|
||||
URI uri = backendNodes.get(nodeName);
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("Invalid node: " + nodeName);
|
||||
}
|
||||
log.debugf("Load balancer: enabling node %s", nodeName);
|
||||
lb.addHost(uri, nodeName);
|
||||
}
|
||||
|
||||
public void disableBackendNodeByName(String nodeName) {
|
||||
URI uri = backendNodes.get(nodeName);
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("Invalid node: " + nodeName);
|
||||
}
|
||||
log.debugf("Load balancer: disabling node %s", nodeName);
|
||||
lb.removeHost(uri);
|
||||
}
|
||||
|
||||
static Map<String, URI> parseNodes(String nodes) {
|
||||
StringTokenizer st = new StringTokenizer(nodes, ",");
|
||||
Map<String, URI> result = new LinkedHashMap<>();
|
||||
|
||||
while (st.hasMoreElements()) {
|
||||
String nodeStr = st.nextToken();
|
||||
String[] node = nodeStr.trim().split("=", 2);
|
||||
result.put(node[0].trim(), URI.create(node[1].trim()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private HttpHandler createHandler(Map<String, String> backendNodes) throws Exception {
|
||||
private HttpHandler createHandler() throws Exception {
|
||||
|
||||
// TODO: configurable options if needed
|
||||
String sessionCookieNames = AuthenticationSessionManager.AUTH_SESSION_ID;
|
||||
|
@ -133,15 +161,7 @@ public class SimpleUndertowLoadBalancer {
|
|||
int connectionIdleTimeout = 60;
|
||||
int maxRetryAttempts = backendNodes.size() - 1;
|
||||
|
||||
final LoadBalancingProxyClient lb = new CustomLoadBalancingClient(new ExclusivityChecker() {
|
||||
|
||||
@Override
|
||||
public boolean isExclusivityRequired(HttpServerExchange exchange) {
|
||||
//we always create a new connection for upgrade requests
|
||||
return exchange.getRequestHeaders().contains(Headers.UPGRADE);
|
||||
}
|
||||
|
||||
}, maxRetryAttempts)
|
||||
lb = new CustomLoadBalancingClient(exchange -> exchange.getRequestHeaders().contains(Headers.UPGRADE), maxRetryAttempts)
|
||||
.setConnectionsPerThread(connectionsPerThread)
|
||||
.setMaxQueueSize(requestQueueSize)
|
||||
.setSoftMaxConnectionsPerThread(cachedConnectionsPerThread)
|
||||
|
@ -152,13 +172,10 @@ public class SimpleUndertowLoadBalancer {
|
|||
lb.addSessionCookieName(id);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> node : backendNodes.entrySet()) {
|
||||
String route = node.getKey();
|
||||
URI uri = new URI(node.getValue());
|
||||
|
||||
backendNodes.forEach((route, uri) -> {
|
||||
lb.addHost(uri, route);
|
||||
log.infof("Added host: %s, route: %s", uri.toString(), route);
|
||||
}
|
||||
log.debugf("Added host: %s, route: %s", uri.toString(), route);
|
||||
});
|
||||
|
||||
ProxyHandler handler = new ProxyHandler(lb, maxTime, ResponseCodeHandler.HANDLE_404);
|
||||
return handler;
|
||||
|
|
|
@ -19,13 +19,17 @@ package org.keycloak.testsuite.arquillian.undertow.lb;
|
|||
|
||||
import org.arquillian.undertow.UndertowContainerConfiguration;
|
||||
import org.jboss.arquillian.container.spi.ConfigurationException;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerConfiguration {
|
||||
|
||||
protected static final Logger log = Logger.getLogger(SimpleUndertowLoadBalancerConfiguration.class);
|
||||
|
||||
private String nodes = SimpleUndertowLoadBalancer.DEFAULT_NODES;
|
||||
private int bindHttpPortOffset = 0;
|
||||
|
||||
public String getNodes() {
|
||||
return nodes;
|
||||
|
@ -35,6 +39,14 @@ public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerCo
|
|||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public int getBindHttpPortOffset() {
|
||||
return bindHttpPortOffset;
|
||||
}
|
||||
|
||||
public void setBindHttpPortOffset(int bindHttpPortOffset) {
|
||||
this.bindHttpPortOffset = bindHttpPortOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws ConfigurationException {
|
||||
super.validate();
|
||||
|
@ -44,5 +56,11 @@ public class SimpleUndertowLoadBalancerConfiguration extends UndertowContainerCo
|
|||
} catch (Exception e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
|
||||
int basePort = getBindHttpPort();
|
||||
int newPort = basePort + bindHttpPortOffset;
|
||||
setBindHttpPort(newPort);
|
||||
log.info("SimpleUndertowLoadBalancer will listen on port: " + newPort);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,13 +25,14 @@ import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaD
|
|||
import org.jboss.logging.Logger;
|
||||
import org.jboss.shrinkwrap.api.Archive;
|
||||
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
|
||||
import org.keycloak.testsuite.arquillian.LoadBalancerController;
|
||||
|
||||
/**
|
||||
* Arquillian container over {@link SimpleUndertowLoadBalancer}
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<SimpleUndertowLoadBalancerConfiguration> {
|
||||
public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<SimpleUndertowLoadBalancerConfiguration>, LoadBalancerController {
|
||||
|
||||
private static final Logger log = Logger.getLogger(SimpleUndertowLoadBalancerContainer.class);
|
||||
|
||||
|
@ -84,4 +85,24 @@ public class SimpleUndertowLoadBalancerContainer implements DeployableContainer<
|
|||
public void undeploy(Descriptor descriptor) throws DeploymentException {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAllBackendNodes() {
|
||||
this.container.enableAllBackendNodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableAllBackendNodes() {
|
||||
this.container.disableAllBackendNodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBackendNodeByName(String nodeName) {
|
||||
this.container.enableBackendNodeByName(nodeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBackendNodeByName(String nodeName) {
|
||||
this.container.disableBackendNodeByName(nodeName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<assembly>
|
||||
|
||||
<id>${cache.server.jboss}</id>
|
||||
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${cache.server.jboss.home}</directory>
|
||||
<outputDirectory>cache-server-${cache.server}</outputDirectory>
|
||||
<excludes>
|
||||
<exclude>**/*.sh</exclude>
|
||||
</excludes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${cache.server.jboss.home}</directory>
|
||||
<outputDirectory>cache-server-${cache.server}</outputDirectory>
|
||||
<includes>
|
||||
<include>**/*.sh</include>
|
||||
</includes>
|
||||
<fileMode>0755</fileMode>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
|
||||
</assembly>
|
|
@ -0,0 +1,42 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:xalan="http://xml.apache.org/xalan"
|
||||
version="2.0"
|
||||
exclude-result-prefixes="xalan">
|
||||
|
||||
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||
<xsl:strip-space elements="*"/>
|
||||
|
||||
<xsl:variable name="nsCacheServer" select="'urn:infinispan:server:core:'"/>
|
||||
|
||||
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsCacheServer)]
|
||||
/*[local-name()='cache-container' and starts-with(namespace-uri(), $nsCacheServer) and @name='local']">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@* | node()" />
|
||||
<local-cache name="work" start="EAGER" batching="false" />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()" />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
|
@ -0,0 +1,40 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:xalan="http://xml.apache.org/xalan"
|
||||
version="2.0"
|
||||
exclude-result-prefixes="xalan">
|
||||
|
||||
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||
<xsl:strip-space elements="*"/>
|
||||
|
||||
<xsl:param name="worker.io-threads" select="'16'"/>
|
||||
<xsl:param name="worker.task-max-threads" select="'128'"/>
|
||||
|
||||
<!--set worker threads-->
|
||||
<xsl:template match="//*[local-name()='worker' and @name='default']">
|
||||
<worker name="default" io-threads="{$worker.io-threads}" task-max-threads="{$worker.task-max-threads}" />
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()" />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<parent>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>integration-arquillian-servers-cache-server-infinispan</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cache Server - JBoss - Infinispan</name>
|
||||
|
||||
<properties>
|
||||
<cache.server>infinispan</cache.server>
|
||||
<cache.server.container>cache-server-${cache.server}</cache.server.container>
|
||||
<cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
|
||||
|
||||
<cache.server.jboss.groupId>org.infinispan.server</cache.server.jboss.groupId>
|
||||
<cache.server.jboss.artifactId>infinispan-server</cache.server.jboss.artifactId>
|
||||
<cache.server.jboss.version>${infinispan.version}</cache.server.jboss.version>
|
||||
<cache.server.jboss.unpacked.folder.name>${cache.server.jboss.artifactId}-${infinispan.version}</cache.server.jboss.unpacked.folder.name>
|
||||
|
||||
<cache.server.worker.io-threads>${cache.default.worker.io-threads}</cache.server.worker.io-threads>
|
||||
<cache.server.worker.task-max-threads>${cache.default.worker.task-max-threads}</cache.server.worker.task-max-threads>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,210 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<parent>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-servers-cache-server</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>integration-arquillian-servers-cache-server-jboss</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cache Server - JBoss Family</name>
|
||||
|
||||
<properties>
|
||||
<common.resources>${project.parent.basedir}/common</common.resources>
|
||||
<assembly.xml>${project.parent.basedir}/assembly.xml</assembly.xml>
|
||||
<cache.server.jboss.home>${containers.home}/${cache.server.jboss.unpacked.folder.name}</cache.server.jboss.home>
|
||||
<security.xslt>security.xsl</security.xslt>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
|
||||
<profile>
|
||||
<id>cache-server-jboss-submodules</id>
|
||||
<activation>
|
||||
<file>
|
||||
<exists>src</exists>
|
||||
</file>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireProperty>
|
||||
<property>cache.server</property>
|
||||
<property>cache.server.jboss.groupId</property>
|
||||
<property>cache.server.jboss.artifactId</property>
|
||||
<property>cache.server.jboss.version</property>
|
||||
<property>cache.server.jboss.unpacked.folder.name</property>
|
||||
</requireProperty>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-cache-server</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>${cache.server.jboss.groupId}</groupId>
|
||||
<artifactId>${cache.server.jboss.artifactId}</artifactId>
|
||||
<version>${cache.server.jboss.version}</version>
|
||||
<type>zip</type>
|
||||
<classifier>bin</classifier>
|
||||
<outputDirectory>${containers.home}</outputDirectory>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>xml-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>configure-adapter-debug-log</id>
|
||||
<phase>process-test-resources</phase>
|
||||
<goals>
|
||||
<goal>transform</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformationSets>
|
||||
<transformationSet>
|
||||
<dir>${cache.server.jboss.home}/standalone/configuration</dir>
|
||||
<includes>
|
||||
<include>standalone.xml</include>
|
||||
</includes>
|
||||
<stylesheet>${common.resources}/add-keycloak-remote-store.xsl</stylesheet>
|
||||
<outputDir>${cache.server.jboss.home}/standalone/configuration</outputDir>
|
||||
</transformationSet>
|
||||
</transformationSets>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>io-worker-threads</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>transform</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformationSets>
|
||||
<transformationSet>
|
||||
<dir>${cache.server.jboss.home}/standalone/configuration</dir>
|
||||
<includes>
|
||||
<include>standalone.xml</include>
|
||||
<include>standalone-ha.xml</include>
|
||||
</includes>
|
||||
<stylesheet>${common.resources}/io.xsl</stylesheet>
|
||||
<outputDir>${cache.server.jboss.home}/standalone/configuration</outputDir>
|
||||
<parameters>
|
||||
<parameter>
|
||||
<name>worker.io-threads</name>
|
||||
<value>${cache.server.worker.io-threads}</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>worker.task-max-threads</name>
|
||||
<value>${cache.server.worker.task-max-threads}</value>
|
||||
</parameter>
|
||||
</parameters>
|
||||
</transformationSet>
|
||||
</transformationSets>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enable-jboss-mgmt-admin</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${cache.server.jboss.home}/standalone/configuration</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${common.resources}</directory>
|
||||
<includes>
|
||||
<include>mgmt-users.properties</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<overwrite>true</overwrite>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>create-zip</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>${assembly.xml}</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>cache-server-infinispan</id>
|
||||
<modules>
|
||||
<module>infinispan</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<parent>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-servers</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>integration-arquillian-servers-cache-server</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cache Server</name>
|
||||
|
||||
<properties>
|
||||
<auth.server.worker.io-threads>${jboss.default.worker.io-threads}</auth.server.worker.io-threads>
|
||||
<auth.server.worker.task-max-threads>${jboss.default.worker.task-max-threads}</auth.server.worker.task-max-threads>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>jboss</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
|
@ -46,13 +46,20 @@
|
|||
<!--<fuse62.version>6.2.0.redhat-133</fuse62.version>-->
|
||||
<fuse62.version>6.2.1.redhat-084</fuse62.version>
|
||||
|
||||
<!-- cache server versions -->
|
||||
<infinispan.version>9.0.1.Final</infinispan.version>
|
||||
|
||||
<jboss.default.worker.io-threads>16</jboss.default.worker.io-threads>
|
||||
<jboss.default.worker.task-max-threads>128</jboss.default.worker.task-max-threads>
|
||||
|
||||
<cache.default.worker.io-threads>2</cache.default.worker.io-threads>
|
||||
<cache.default.worker.task-max-threads>4</cache.default.worker.task-max-threads>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>auth-server</module>
|
||||
<module>app-server</module>
|
||||
<module>cache-server</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
<exclude.client>-</exclude.client>
|
||||
<!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
|
||||
<exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
|
||||
<exclude.crossdc>**/crossdc/**/*Test.java</exclude.crossdc>
|
||||
<!-- exclude x509 tests by default, enabled by 'ssl' profile -->
|
||||
<exclude.x509>**/x509/*Test.java</exclude.x509>
|
||||
<!-- exclude undertow adapter tests. They can be added by -Dtest=org.keycloak.testsuite.adapter.undertow.**.*Test -->
|
||||
|
@ -125,6 +126,7 @@
|
|||
<exclude>${exclude.account}</exclude>
|
||||
<exclude>${exclude.client}</exclude>
|
||||
<exclude>${exclude.cluster}</exclude>
|
||||
<exclude>${exclude.crossdc</exclude>
|
||||
<exclude>${exclude.undertow.adapter}</exclude>
|
||||
<exclude>${exclude.x509}</exclude>
|
||||
</excludes>
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.arquillian;
|
||||
|
||||
import org.jboss.arquillian.container.spi.Container;
|
||||
import org.jboss.arquillian.container.spi.ContainerRegistry;
|
||||
import org.jboss.arquillian.container.spi.event.StartContainer;
|
||||
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
||||
|
@ -41,10 +40,11 @@ import org.keycloak.testsuite.util.OAuthClient;
|
|||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
|
||||
/**
|
||||
|
@ -68,8 +68,18 @@ public class AuthServerTestEnricher {
|
|||
private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container";
|
||||
public static final String AUTH_SERVER_CONTAINER = System.getProperty(AUTH_SERVER_CONTAINER_PROPERTY, AUTH_SERVER_CONTAINER_DEFAULT);
|
||||
|
||||
private static final String AUTH_SERVER_BACKEND_DEFAULT = AUTH_SERVER_CONTAINER + "-backend";
|
||||
private static final String AUTH_SERVER_BACKEND_PROPERTY = "auth.server.backend";
|
||||
public static final String AUTH_SERVER_BACKEND = System.getProperty(AUTH_SERVER_BACKEND_PROPERTY, AUTH_SERVER_BACKEND_DEFAULT);
|
||||
|
||||
private static final String AUTH_SERVER_BALANCER_DEFAULT = "auth-server-balancer";
|
||||
private static final String AUTH_SERVER_BALANCER_PROPERTY = "auth.server.balancer";
|
||||
public static final String AUTH_SERVER_BALANCER = System.getProperty(AUTH_SERVER_BALANCER_PROPERTY, AUTH_SERVER_BALANCER_DEFAULT);
|
||||
|
||||
private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster";
|
||||
public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false"));
|
||||
private static final String AUTH_SERVER_CROSS_DC_PROPERTY = "auth.server.crossdc";
|
||||
public static final boolean AUTH_SERVER_CROSS_DC = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CROSS_DC_PROPERTY, "false"));
|
||||
|
||||
private static final boolean AUTH_SERVER_UNDERTOW_CLUSTER = Boolean.parseBoolean(System.getProperty("auth.server.undertow.cluster", "false"));
|
||||
|
||||
|
@ -106,46 +116,82 @@ public class AuthServerTestEnricher {
|
|||
}
|
||||
|
||||
public void initializeSuiteContext(@Observes(precedence = 2) BeforeSuite event) {
|
||||
|
||||
Set<ContainerInfo> containers = new LinkedHashSet<>();
|
||||
for (Container c : containerRegistry.get().getContainers()) {
|
||||
containers.add(new ContainerInfo(c));
|
||||
}
|
||||
Set<ContainerInfo> containers = containerRegistry.get().getContainers().stream()
|
||||
.map(ContainerInfo::new)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
suiteContext = new SuiteContext(containers);
|
||||
|
||||
String authServerFrontend = null;
|
||||
if (AUTH_SERVER_CROSS_DC) {
|
||||
// if cross-dc mode enabled, load-balancer is the frontend of datacenter cluster
|
||||
containers.stream()
|
||||
.filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BALANCER + "-cross-dc"))
|
||||
.forEach(c -> {
|
||||
String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
|
||||
String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
|
||||
updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
|
||||
suiteContext.addAuthServerInfo(Integer.valueOf(dcString), c);
|
||||
});
|
||||
|
||||
if (AUTH_SERVER_CLUSTER) {
|
||||
if (suiteContext.getDcAuthServerInfo().isEmpty()) {
|
||||
throw new IllegalStateException("Not found frontend container (load balancer): " + AUTH_SERVER_BALANCER);
|
||||
}
|
||||
if (suiteContext.getDcAuthServerInfo().stream().anyMatch(Objects::isNull)) {
|
||||
throw new IllegalStateException("Frontend container (load balancer) misconfiguration");
|
||||
}
|
||||
|
||||
containers.stream()
|
||||
.filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER + "-cross-dc-"))
|
||||
.forEach(c -> {
|
||||
String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
|
||||
String dcString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("dataCenter", "0");
|
||||
updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
|
||||
suiteContext.addAuthServerBackendsInfo(Integer.valueOf(dcString), c);
|
||||
});
|
||||
|
||||
if (suiteContext.getDcAuthServerInfo().isEmpty()) {
|
||||
throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", AUTH_SERVER_BACKEND));
|
||||
}
|
||||
if (suiteContext.getDcAuthServerBackendsInfo().stream().anyMatch(Objects::isNull)) {
|
||||
throw new IllegalStateException("Frontend container (load balancer) misconfiguration");
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
log.info("Using frontend containers: " + this.suiteContext.getDcAuthServerInfo().stream()
|
||||
.map(ContainerInfo::getQualifier)
|
||||
.collect(Collectors.joining(", ")));
|
||||
} else if (AUTH_SERVER_CLUSTER) {
|
||||
// if cluster mode enabled, load-balancer is the frontend
|
||||
for (ContainerInfo c : containers) {
|
||||
if (c.getQualifier().startsWith("auth-server-balancer")) {
|
||||
authServerFrontend = c.getQualifier();
|
||||
}
|
||||
}
|
||||
|
||||
if (authServerFrontend != null) {
|
||||
log.info("Using frontend container: " + authServerFrontend);
|
||||
} else {
|
||||
throw new IllegalStateException("Not found frontend container");
|
||||
}
|
||||
} else {
|
||||
authServerFrontend = AUTH_SERVER_CONTAINER; // single-node mode
|
||||
}
|
||||
|
||||
String authServerBackend = AUTH_SERVER_CONTAINER + "-backend";
|
||||
int backends = 0;
|
||||
for (ContainerInfo container : suiteContext.getContainers()) {
|
||||
// frontend
|
||||
if (container.getQualifier().equals(authServerFrontend)) {
|
||||
ContainerInfo container = containers.stream()
|
||||
.filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BALANCER))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalStateException("Not found frontend container: " + AUTH_SERVER_BALANCER));
|
||||
updateWithAuthServerInfo(container);
|
||||
suiteContext.setAuthServerInfo(container);
|
||||
|
||||
containers.stream()
|
||||
.filter(c -> c.getQualifier().startsWith(AUTH_SERVER_BACKEND))
|
||||
.forEach(c -> {
|
||||
String portOffsetString = c.getArquillianContainer().getContainerConfiguration().getContainerProperties().getOrDefault("bindHttpPortOffset", "0");
|
||||
updateWithAuthServerInfo(c, Integer.valueOf(portOffsetString));
|
||||
suiteContext.addAuthServerBackendsInfo(0, c);
|
||||
});
|
||||
|
||||
if (suiteContext.getAuthServerBackendsInfo().isEmpty()) {
|
||||
throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", AUTH_SERVER_BACKEND));
|
||||
}
|
||||
// backends
|
||||
if (AUTH_SERVER_CLUSTER && container.getQualifier().startsWith(authServerBackend)) {
|
||||
updateWithAuthServerInfo(container, ++backends);
|
||||
suiteContext.getAuthServerBackendsInfo().add(container);
|
||||
}
|
||||
|
||||
log.info("Using frontend container: " + container.getQualifier());
|
||||
} else {
|
||||
// frontend-only
|
||||
ContainerInfo container = containers.stream()
|
||||
.filter(c -> c.getQualifier().startsWith(AUTH_SERVER_CONTAINER))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalStateException("Not found frontend container: " + AUTH_SERVER_CONTAINER));
|
||||
updateWithAuthServerInfo(container);
|
||||
suiteContext.setAuthServerInfo(container);
|
||||
}
|
||||
|
||||
// Setup with 2 undertow backend nodes and no loadbalancer.
|
||||
|
@ -153,14 +199,6 @@ public class AuthServerTestEnricher {
|
|||
// suiteContext.setAuthServerInfo(suiteContext.getAuthServerBackendsInfo().get(0));
|
||||
// }
|
||||
|
||||
// validate auth server setup
|
||||
if (suiteContext.getAuthServerInfo() == null) {
|
||||
throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", authServerFrontend));
|
||||
}
|
||||
if (AUTH_SERVER_CLUSTER && suiteContext.getAuthServerBackendsInfo().isEmpty()) {
|
||||
throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend));
|
||||
}
|
||||
|
||||
if (START_MIGRATION_CONTAINER) {
|
||||
// init migratedAuthServerInfo
|
||||
for (ContainerInfo container : suiteContext.getContainers()) {
|
||||
|
|
|
@ -5,6 +5,8 @@ import org.jboss.arquillian.container.spi.Container;
|
|||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import org.jboss.arquillian.container.spi.Container.State;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -97,4 +99,12 @@ public class ContainerInfo {
|
|||
other.arquillianContainer.getContainerConfiguration().getContainerName());
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return arquillianContainer.getState() == State.STARTED;
|
||||
}
|
||||
|
||||
public boolean isManual() {
|
||||
return Objects.equals(arquillianContainer.getContainerConfiguration().getMode(), "manual");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.testsuite.arquillian.h2.H2TestEnricher;
|
|||
import org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer;
|
||||
import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
|
||||
import org.keycloak.testsuite.arquillian.provider.AdminClientProvider;
|
||||
import org.keycloak.testsuite.arquillian.provider.LoadBalancerControllerProvider;
|
||||
import org.keycloak.testsuite.arquillian.provider.OAuthClientProvider;
|
||||
import org.keycloak.testsuite.arquillian.provider.SuiteContextProvider;
|
||||
import org.keycloak.testsuite.arquillian.provider.TestContextProvider;
|
||||
|
@ -57,7 +58,8 @@ public class KeycloakArquillianExtension implements LoadableExtension {
|
|||
.service(ResourceProvider.class, SuiteContextProvider.class)
|
||||
.service(ResourceProvider.class, TestContextProvider.class)
|
||||
.service(ResourceProvider.class, AdminClientProvider.class)
|
||||
.service(ResourceProvider.class, OAuthClientProvider.class);
|
||||
.service(ResourceProvider.class, OAuthClientProvider.class)
|
||||
.service(ResourceProvider.class, LoadBalancerControllerProvider.class);
|
||||
|
||||
builder
|
||||
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Set;
|
|||
|
||||
import org.keycloak.testsuite.arquillian.migration.MigrationContext;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import static org.keycloak.testsuite.util.MailServerConfiguration.FROM;
|
||||
import static org.keycloak.testsuite.util.MailServerConfiguration.HOST;
|
||||
import static org.keycloak.testsuite.util.MailServerConfiguration.PORT;
|
||||
|
@ -36,8 +37,8 @@ public final class SuiteContext {
|
|||
|
||||
private final Set<ContainerInfo> container;
|
||||
|
||||
private ContainerInfo authServerInfo;
|
||||
private final List<ContainerInfo> authServerBackendsInfo = new ArrayList<>();
|
||||
private List<ContainerInfo> authServerInfo = new LinkedList<>();
|
||||
private final List<List<ContainerInfo>> authServerBackendsInfo = new ArrayList<>();
|
||||
|
||||
private ContainerInfo migratedAuthServerInfo;
|
||||
private final MigrationContext migrationContext = new MigrationContext();
|
||||
|
@ -72,17 +73,48 @@ public final class SuiteContext {
|
|||
}
|
||||
|
||||
public ContainerInfo getAuthServerInfo() {
|
||||
return getAuthServerInfo(0);
|
||||
}
|
||||
|
||||
public ContainerInfo getAuthServerInfo(int dcIndex) {
|
||||
return authServerInfo.get(dcIndex);
|
||||
}
|
||||
|
||||
public List<ContainerInfo> getDcAuthServerInfo() {
|
||||
return authServerInfo;
|
||||
}
|
||||
|
||||
public void setAuthServerInfo(ContainerInfo authServerInfo) {
|
||||
this.authServerInfo = authServerInfo;
|
||||
this.authServerInfo = new LinkedList<>();
|
||||
this.authServerInfo.add(authServerInfo);
|
||||
}
|
||||
|
||||
public void addAuthServerInfo(int dcIndex, ContainerInfo serverInfo) {
|
||||
while (dcIndex >= authServerInfo.size()) {
|
||||
authServerInfo.add(null);
|
||||
}
|
||||
this.authServerInfo.set(dcIndex, serverInfo);
|
||||
}
|
||||
|
||||
public List<ContainerInfo> getAuthServerBackendsInfo() {
|
||||
return getAuthServerBackendsInfo(0);
|
||||
}
|
||||
|
||||
public List<ContainerInfo> getAuthServerBackendsInfo(int dcIndex) {
|
||||
return authServerBackendsInfo.get(dcIndex);
|
||||
}
|
||||
|
||||
public List<List<ContainerInfo>> getDcAuthServerBackendsInfo() {
|
||||
return authServerBackendsInfo;
|
||||
}
|
||||
|
||||
public void addAuthServerBackendsInfo(int dcIndex, ContainerInfo container) {
|
||||
while (dcIndex >= authServerBackendsInfo.size()) {
|
||||
authServerBackendsInfo.add(new LinkedList<>());
|
||||
}
|
||||
authServerBackendsInfo.get(dcIndex).add(container);
|
||||
}
|
||||
|
||||
public ContainerInfo getMigratedAuthServerInfo() {
|
||||
return migratedAuthServerInfo;
|
||||
}
|
||||
|
@ -99,6 +131,10 @@ public final class SuiteContext {
|
|||
return ! authServerBackendsInfo.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isAuthServerCrossDc() {
|
||||
return authServerBackendsInfo.size() > 1;
|
||||
}
|
||||
|
||||
public boolean isAuthServerMigrationEnabled() {
|
||||
return migratedAuthServerInfo != null;
|
||||
}
|
||||
|
@ -113,19 +149,38 @@ public final class SuiteContext {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
String containers = "Auth server: " + (isAuthServerCluster() ? "\nFrontend: " : "")
|
||||
+ authServerInfo.getQualifier() + "\n";
|
||||
for (ContainerInfo bInfo : getAuthServerBackendsInfo()) {
|
||||
containers += "Backend: " + bInfo + "\n";
|
||||
StringBuilder sb = new StringBuilder("SUITE CONTEXT:\nAuth server: ");
|
||||
|
||||
if (isAuthServerCrossDc()) {
|
||||
for (int i = 0; i < authServerInfo.size(); i ++) {
|
||||
ContainerInfo frontend = this.authServerInfo.get(i);
|
||||
sb.append("\nFrontend (dc=").append(i).append("): ").append(frontend.getQualifier()).append("\n");
|
||||
}
|
||||
|
||||
for (int i = 0; i < authServerBackendsInfo.size(); i ++) {
|
||||
int dcIndex = i;
|
||||
getDcAuthServerBackendsInfo().get(i).forEach(bInfo -> sb.append("Backend (dc=").append(dcIndex).append("): ").append(bInfo).append("\n"));
|
||||
}
|
||||
} else if (isAuthServerCluster()) {
|
||||
sb.append(isAuthServerCluster() ? "\nFrontend: " : "")
|
||||
.append(getAuthServerInfo().getQualifier())
|
||||
.append("\n");
|
||||
|
||||
getAuthServerBackendsInfo().forEach(bInfo -> sb.append(" Backend: ").append(bInfo).append("\n"));
|
||||
} else {
|
||||
sb.append(getAuthServerInfo().getQualifier())
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
|
||||
if (isAuthServerMigrationEnabled()) {
|
||||
containers += "Migrated from: " + System.getProperty("migrated.auth.server.version") + "\n";
|
||||
sb.append("Migrated from: ").append(System.getProperty("migrated.auth.server.version")).append("\n");
|
||||
}
|
||||
|
||||
if (isAdapterCompatTesting()) {
|
||||
containers += "Adapter backward compatibility testing mode!\n";
|
||||
sb.append("Adapter backward compatibility testing mode!\n");
|
||||
}
|
||||
return "SUITE CONTEXT:\n"
|
||||
+ containers;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.testsuite.arquillian.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD })
|
||||
public @interface LoadBalancer {
|
||||
String value() default "";
|
||||
}
|
|
@ -110,6 +110,8 @@ public class RegistryCreator {
|
|||
if (isClassPresent(getAdapterImplClassValue(containerDef))) {
|
||||
return DeployableContainer.class.isAssignableFrom(
|
||||
loadClass(getAdapterImplClassValue(containerDef)));
|
||||
} else {
|
||||
log.warn("Cannot load adapterImpl class for " + containerDef.getContainerName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package org.keycloak.testsuite.arquillian.provider;
|
||||
|
||||
import org.keycloak.testsuite.arquillian.annotation.LoadBalancer;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.jboss.arquillian.container.spi.event.KillContainer;
|
||||
import org.jboss.arquillian.container.spi.event.StartContainer;
|
||||
import org.jboss.arquillian.container.spi.event.StopContainer;
|
||||
import org.jboss.arquillian.core.api.Instance;
|
||||
import org.jboss.arquillian.core.api.annotation.Inject;
|
||||
import org.jboss.arquillian.core.api.annotation.Observes;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
|
||||
import org.keycloak.testsuite.arquillian.LoadBalancerController;
|
||||
import org.jboss.arquillian.container.spi.Container;
|
||||
import org.jboss.arquillian.container.spi.ContainerRegistry;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class LoadBalancerControllerProvider implements ResourceProvider {
|
||||
|
||||
@Inject
|
||||
private Instance<ContainerRegistry> registry;
|
||||
|
||||
@Override
|
||||
public boolean canProvide(Class<?> type) {
|
||||
return type.equals(LoadBalancerController.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object lookup(ArquillianResource resource, Annotation... qualifiers) {
|
||||
String balancerName = null;
|
||||
|
||||
// Check for the presence of possible qualifiers
|
||||
for (Annotation a : qualifiers) {
|
||||
Class<? extends Annotation> annotationType = a.annotationType();
|
||||
|
||||
if (annotationType.equals(LoadBalancer.class)) {
|
||||
balancerName = ((LoadBalancer) a).value();
|
||||
}
|
||||
}
|
||||
|
||||
ContainerRegistry reg = registry.get();
|
||||
Container container = null;
|
||||
if (balancerName == null || "".equals(balancerName.trim())) {
|
||||
if (reg.getContainers().size() == 1) {
|
||||
container = reg.getContainers().get(0);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid load balancer configuration request - need to specify load balancer name in @LoadBalancerController");
|
||||
}
|
||||
} else {
|
||||
container = reg.getContainer(balancerName);
|
||||
}
|
||||
|
||||
if (container == null) {
|
||||
throw new IllegalArgumentException("Invalid load balancer configuration - load balancer not found: '" + balancerName + "'");
|
||||
}
|
||||
if (! (container.getDeployableContainer() instanceof LoadBalancerController)) {
|
||||
throw new IllegalArgumentException("Invalid load balancer configuration - container " + container.getName() + " is not a load balancer");
|
||||
}
|
||||
|
||||
return container.getDeployableContainer();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SetSystemProperty implements Closeable {
|
||||
|
||||
private final String name;
|
||||
private final String oldValue;
|
||||
|
||||
public SetSystemProperty(String name, String value) {
|
||||
this.name = name;
|
||||
this.oldValue = System.getProperty(name);
|
||||
|
||||
if (value == null) {
|
||||
if (oldValue != null) {
|
||||
System.getProperties().remove(name);
|
||||
}
|
||||
} else {
|
||||
System.setProperty(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void revert() {
|
||||
String value = System.getProperty(name);
|
||||
|
||||
if (oldValue == null) {
|
||||
if (value != null) {
|
||||
System.getProperties().remove(name);
|
||||
}
|
||||
} else {
|
||||
System.setProperty(name, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
revert();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.testsuite.crossdc;
|
||||
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||
import org.keycloak.testsuite.util.TestCleanup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.Before;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public abstract class AbstractAdminCrossDCTest extends AbstractCrossDCTest {
|
||||
|
||||
protected static final String REALM_NAME = "admin-client-test";
|
||||
|
||||
protected RealmResource realm;
|
||||
protected String realmId;
|
||||
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
findTestApp(testRealm).setDirectAccessGrantsEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
super.addTestRealms(testRealms);
|
||||
|
||||
RealmRepresentation adminRealmRep = new RealmRepresentation();
|
||||
adminRealmRep.setId(REALM_NAME);
|
||||
adminRealmRep.setRealm(REALM_NAME);
|
||||
adminRealmRep.setEnabled(true);
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put("from", "auto@keycloak.org");
|
||||
config.put("host", "localhost");
|
||||
config.put("port", "3025");
|
||||
adminRealmRep.setSmtpServer(config);
|
||||
|
||||
List<String> eventListeners = new ArrayList<>();
|
||||
eventListeners.add(JBossLoggingEventListenerProviderFactory.ID);
|
||||
eventListeners.add(EventsListenerProviderFactory.PROVIDER_ID);
|
||||
adminRealmRep.setEventsListeners(eventListeners);
|
||||
|
||||
testRealms.add(adminRealmRep);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setRealm() {
|
||||
realm = adminClient.realm(REALM_NAME);
|
||||
realmId = realm.toRepresentation().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestCleanup getCleanup() {
|
||||
return getCleanup(REALM_NAME);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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.testsuite.crossdc;
|
||||
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||
import org.keycloak.testsuite.arquillian.LoadBalancerController;
|
||||
import org.keycloak.testsuite.arquillian.annotation.LoadBalancer;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.jboss.arquillian.container.test.api.ContainerController;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@ArquillianResource
|
||||
@LoadBalancer(value = "auth-server-balancer-cross-dc")
|
||||
protected LoadBalancerController loadBalancerCtrl;
|
||||
|
||||
@ArquillianResource
|
||||
protected ContainerController containerController;
|
||||
|
||||
protected Map<ContainerInfo, Keycloak> backendAdminClients = new HashMap<>();
|
||||
|
||||
@After
|
||||
@Before
|
||||
public void enableOnlyFirstNodeInFirstDc() {
|
||||
this.loadBalancerCtrl.disableAllBackendNodes();
|
||||
loadBalancerCtrl.enableBackendNodeByName(getAutomaticallyStartedBackendNodes(0)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No node is started automatically"))
|
||||
.getQualifier()
|
||||
);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void terminateManuallyStartedServers() {
|
||||
log.debug("Halting all nodes that are started manually");
|
||||
this.suiteContext.getDcAuthServerBackendsInfo().stream()
|
||||
.flatMap(List::stream)
|
||||
.filter(ContainerInfo::isStarted)
|
||||
.filter(ContainerInfo::isManual)
|
||||
.map(ContainerInfo::getQualifier)
|
||||
.forEach(containerController::stop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importTestRealms() {
|
||||
enableOnlyFirstNodeInFirstDc();
|
||||
super.importTestRealms();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAbstractKeycloakTest() {
|
||||
enableOnlyFirstNodeInFirstDc();
|
||||
super.afterAbstractKeycloakTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCookies() {
|
||||
enableOnlyFirstNodeInFirstDc();
|
||||
super.deleteCookies();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void initLoadBalancer() {
|
||||
log.debug("Initializing load balancer - only enabling started nodes in the first DC");
|
||||
this.loadBalancerCtrl.disableAllBackendNodes();
|
||||
// Enable only the started nodes in each datacenter
|
||||
this.suiteContext.getDcAuthServerBackendsInfo().get(0).stream()
|
||||
.filter(ContainerInfo::isStarted)
|
||||
.map(ContainerInfo::getQualifier)
|
||||
.forEach(loadBalancerCtrl::enableBackendNodeByName);
|
||||
}
|
||||
|
||||
protected Keycloak createAdminClientFor(ContainerInfo node) {
|
||||
log.info("Initializing admin client for " + node.getContextRoot() + "/auth");
|
||||
return Keycloak.getInstance(node.getContextRoot() + "/auth", AuthRealm.MASTER, AuthRealm.ADMIN, AuthRealm.ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
|
||||
}
|
||||
|
||||
protected Keycloak getAdminClientFor(ContainerInfo node) {
|
||||
Keycloak adminClient = backendAdminClients.get(node);
|
||||
if (adminClient == null && node.equals(suiteContext.getAuthServerInfo())) {
|
||||
adminClient = this.adminClient;
|
||||
}
|
||||
return adminClient;
|
||||
}
|
||||
|
||||
public void disableDcOnLoadBalancer(int dcIndex) {
|
||||
log.infof("Disabling load balancer for dc=%d", dcIndex);
|
||||
this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).forEach(c -> loadBalancerCtrl.disableBackendNodeByName(c.getQualifier()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables all started nodes in the given data center
|
||||
* @param dcIndex
|
||||
*/
|
||||
public void enableDcOnLoadBalancer(int dcIndex) {
|
||||
log.infof("Enabling load balancer for dc=%d", dcIndex);
|
||||
final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
|
||||
if (! dcNodes.stream().anyMatch(ContainerInfo::isStarted)) {
|
||||
log.warnf("No node is started in DC %d", dcIndex);
|
||||
} else {
|
||||
dcNodes.stream()
|
||||
.filter(ContainerInfo::isStarted)
|
||||
.forEach(c -> loadBalancerCtrl.enableBackendNodeByName(c.getQualifier()));
|
||||
}
|
||||
}
|
||||
|
||||
public void disableLoadBalancerNode(int dcIndex, int nodeIndex) {
|
||||
log.infof("Disabling load balancer for dc=%d, node=%d", dcIndex, nodeIndex);
|
||||
loadBalancerCtrl.disableBackendNodeByName(this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).get(nodeIndex).getQualifier());
|
||||
}
|
||||
|
||||
public void enableLoadBalancerNode(int dcIndex, int nodeIndex) {
|
||||
log.infof("Enabling load balancer for dc=%d, node=%d", dcIndex, nodeIndex);
|
||||
final ContainerInfo backendNode = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex).get(nodeIndex);
|
||||
if (backendNode == null) {
|
||||
throw new IllegalArgumentException("Invalid node with index " + nodeIndex + " for DC " + dcIndex);
|
||||
}
|
||||
if (! backendNode.isStarted()) {
|
||||
log.warnf("Node %s is not started in DC %d", backendNode.getQualifier(), dcIndex);
|
||||
}
|
||||
loadBalancerCtrl.enableBackendNodeByName(backendNode.getQualifier());
|
||||
}
|
||||
|
||||
public Stream<ContainerInfo> getManuallyStartedBackendNodes(int dcIndex) {
|
||||
final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
|
||||
return dcNodes.stream().filter(ContainerInfo::isManual);
|
||||
}
|
||||
|
||||
public Stream<ContainerInfo> getAutomaticallyStartedBackendNodes(int dcIndex) {
|
||||
final List<ContainerInfo> dcNodes = this.suiteContext.getDcAuthServerBackendsInfo().get(dcIndex);
|
||||
return dcNodes.stream().filter(c -> ! c.isManual());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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.testsuite.crossdc;
|
||||
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Retry;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||
import org.keycloak.testsuite.page.LoginPasswordUpdatePage;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
import org.keycloak.testsuite.util.GreenMailRule;
|
||||
import org.keycloak.testsuite.util.MailUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class ActionTokenCrossDCTest extends AbstractAdminCrossDCTest {
|
||||
|
||||
@Rule
|
||||
public GreenMailRule greenMail = new GreenMailRule();
|
||||
|
||||
@Page
|
||||
protected LoginPasswordUpdatePage passwordUpdatePage;
|
||||
|
||||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
private String createUser(UserRepresentation userRep) {
|
||||
Response response = realm.users().create(userRep);
|
||||
String createdId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
|
||||
getCleanup().addUserId(createdId);
|
||||
|
||||
return createdId;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendResetPasswordEmailSuccessWorksInCrossDc() throws IOException, MessagingException {
|
||||
UserRepresentation userRep = new UserRepresentation();
|
||||
userRep.setEnabled(true);
|
||||
userRep.setUsername("user1");
|
||||
userRep.setEmail("user1@test.com");
|
||||
|
||||
String id = createUser(userRep);
|
||||
|
||||
UserResource user = realm.users().get(id);
|
||||
List<String> actions = new LinkedList<>();
|
||||
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
||||
user.executeActionsEmail(actions);
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||
|
||||
String link = MailUtils.getPasswordResetEmailLink(message);
|
||||
|
||||
driver.navigate().to(link);
|
||||
|
||||
passwordUpdatePage.assertCurrent();
|
||||
|
||||
passwordUpdatePage.changePassword("new-pass", "new-pass");
|
||||
|
||||
assertEquals("Your account has been updated.", driver.getTitle());
|
||||
|
||||
disableDcOnLoadBalancer(0);
|
||||
enableDcOnLoadBalancer(1);
|
||||
|
||||
Retry.execute(() -> {
|
||||
driver.navigate().to(link);
|
||||
errorPage.assertCurrent();
|
||||
}, 3, 400);
|
||||
}
|
||||
|
||||
@Ignore("KEYCLOAK-5030")
|
||||
@Test
|
||||
public void sendResetPasswordEmailAfterNewNodeAdded() throws IOException, MessagingException {
|
||||
disableDcOnLoadBalancer(1);
|
||||
|
||||
UserRepresentation userRep = new UserRepresentation();
|
||||
userRep.setEnabled(true);
|
||||
userRep.setUsername("user1");
|
||||
userRep.setEmail("user1@test.com");
|
||||
|
||||
String id = createUser(userRep);
|
||||
|
||||
UserResource user = realm.users().get(id);
|
||||
List<String> actions = new LinkedList<>();
|
||||
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
||||
user.executeActionsEmail(actions);
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||
|
||||
String link = MailUtils.getPasswordResetEmailLink(message);
|
||||
|
||||
driver.navigate().to(link);
|
||||
|
||||
passwordUpdatePage.assertCurrent();
|
||||
|
||||
passwordUpdatePage.changePassword("new-pass", "new-pass");
|
||||
|
||||
assertEquals("Your account has been updated.", driver.getTitle());
|
||||
|
||||
disableDcOnLoadBalancer(0);
|
||||
getManuallyStartedBackendNodes(1)
|
||||
.findFirst()
|
||||
.ifPresent(c -> {
|
||||
containerController.start(c.getQualifier());
|
||||
loadBalancerCtrl.enableBackendNodeByName(c.getQualifier());
|
||||
});
|
||||
|
||||
driver.navigate().to(link);
|
||||
|
||||
errorPage.assertCurrent();
|
||||
}
|
||||
|
||||
}
|
|
@ -107,16 +107,24 @@
|
|||
|
||||
"connectionsInfinispan": {
|
||||
"default": {
|
||||
"jgroupsUdpMcastAddr": "${keycloak.connectionsInfinispan.jgroupsUdpMcastAddr:234.56.78.90}",
|
||||
"nodeName": "${keycloak.connectionsInfinispan.nodeName,jboss.node.name:defaultNodeName}",
|
||||
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
|
||||
"async": "${keycloak.connectionsInfinispan.async:false}",
|
||||
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:1}",
|
||||
"l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}",
|
||||
"remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}",
|
||||
"remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreHost:localhost}",
|
||||
"remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
|
||||
"remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}"
|
||||
}
|
||||
},
|
||||
|
||||
"stickySessionEncoder": {
|
||||
"infinispan": {
|
||||
"nodeName": "${keycloak.stickySessionEncoder.nodeName,jboss.node.name:defaultNodeName}"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"truststore": {
|
||||
"file": {
|
||||
|
|
|
@ -103,6 +103,7 @@
|
|||
<property name="outputToConsole">${backends.console.output}</property>
|
||||
<property name="managementPort">${auth.server.backend1.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
|
||||
<property name="bindHttpPortOffset">${auth.server.backend1.port.offset}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
<container qualifier="auth-server-${auth.server}-backend2" mode="manual" >
|
||||
|
@ -124,6 +125,7 @@
|
|||
<property name="outputToConsole">${backends.console.output}</property>
|
||||
<property name="managementPort">${auth.server.backend2.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
|
||||
<property name="bindHttpPortOffset">${auth.server.backend2.port.offset}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
</group>
|
||||
|
@ -165,6 +167,138 @@
|
|||
</group>
|
||||
|
||||
|
||||
<!-- Cross DC with embedded undertow. Node numbering is [centre #].[node #] -->
|
||||
<group qualifier="auth-server-undertow-cross-dc">
|
||||
<container qualifier="cache-server-cross-dc" mode="suite" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.undertow.crossdc}</property>
|
||||
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
|
||||
<property name="jbossHome">${cache.server.home}</property>
|
||||
<property name="serverConfig">standalone.xml</property>
|
||||
<property name="jbossArguments">
|
||||
-Djboss.socket.binding.port-offset=${cache.server.port.offset}
|
||||
-Djboss.default.multicast.address=234.56.78.99
|
||||
-Djboss.node.name=cache-server
|
||||
${adapter.test.props}
|
||||
${auth.server.profile}
|
||||
</property>
|
||||
<property name="javaVmArguments">
|
||||
${auth.server.memory.settings}
|
||||
-Djava.net.preferIPv4Stack=true
|
||||
</property>
|
||||
<property name="outputToConsole">${cache.server.console.output}</property>
|
||||
<property name="managementPort">${cache.server.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
|
||||
<container qualifier="auth-server-balancer-cross-dc" mode="suite" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.undertow.crossdc}</property>
|
||||
<property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.lb.SimpleUndertowLoadBalancerContainer</property>
|
||||
<property name="bindAddress">localhost</property>
|
||||
<property name="bindHttpPort">${auth.server.http.port}</property>
|
||||
<property name="bindHttpPortOffset">5</property>
|
||||
<property name="nodes">auth-server-undertow-cross-dc-0.1=http://localhost:8101,auth-server-undertow-cross-dc-0.2-manual=http://localhost:8102,auth-server-undertow-cross-dc-1.1=http://localhost:8111,auth-server-undertow-cross-dc-1.2-manual=http://localhost:8112</property>
|
||||
</configuration>
|
||||
</container>
|
||||
|
||||
<container qualifier="auth-server-undertow-cross-dc-0.1" mode="suite" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.undertow.crossdc}</property>
|
||||
<property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
|
||||
<property name="bindAddress">localhost</property>
|
||||
<property name="bindHttpPort">${auth.server.http.port}</property>
|
||||
<property name="bindHttpPortOffset">-79</property>
|
||||
<property name="route">auth-server-undertow-cross-dc-0.1</property>
|
||||
<property name="remoteMode">${undertow.remote}</property>
|
||||
<property name="dataCenter">0</property>
|
||||
<property name="keycloakConfigPropertyOverrides">{
|
||||
"keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.1",
|
||||
"keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-0.1",
|
||||
"keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
|
||||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
<container qualifier="auth-server-undertow-cross-dc-0.2-manual" mode="manual" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.undertow.crossdc}</property>
|
||||
<property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
|
||||
<property name="bindAddress">localhost</property>
|
||||
<property name="bindHttpPort">${auth.server.http.port}</property>
|
||||
<property name="bindHttpPortOffset">-78</property>
|
||||
<property name="route">auth-server-undertow-cross-dc-0.2</property>
|
||||
<property name="remoteMode">${undertow.remote}</property>
|
||||
<property name="dataCenter">0</property>
|
||||
<property name="keycloakConfigPropertyOverrides">{
|
||||
"keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.1",
|
||||
"keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-0.2",
|
||||
"keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
|
||||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
|
||||
<container qualifier="auth-server-undertow-cross-dc-1.1" mode="suite" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.undertow.crossdc}</property>
|
||||
<property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
|
||||
<property name="bindAddress">localhost</property>
|
||||
<property name="bindHttpPort">${auth.server.http.port}</property>
|
||||
<property name="bindHttpPortOffset">-69</property>
|
||||
<property name="route">auth-server-undertow-cross-dc-1.1</property>
|
||||
<property name="remoteMode">${undertow.remote}</property>
|
||||
<property name="dataCenter">1</property>
|
||||
<property name="keycloakConfigPropertyOverrides">{
|
||||
"keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.2",
|
||||
"keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-1.1",
|
||||
"keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
|
||||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
<container qualifier="auth-server-undertow-cross-dc-1.2-manual" mode="manual" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.undertow.crossdc}</property>
|
||||
<property name="adapterImplClass">org.keycloak.testsuite.arquillian.undertow.KeycloakOnUndertow</property>
|
||||
<property name="bindAddress">localhost</property>
|
||||
<property name="bindHttpPort">${auth.server.http.port}</property>
|
||||
<property name="bindHttpPortOffset">-68</property>
|
||||
<property name="route">auth-server-undertow-cross-dc-1.2</property>
|
||||
<property name="remoteMode">${undertow.remote}</property>
|
||||
<property name="dataCenter">1</property>
|
||||
<property name="keycloakConfigPropertyOverrides">{
|
||||
"keycloak.connectionsInfinispan.jgroupsUdpMcastAddr": "234.56.78.2",
|
||||
"keycloak.connectionsInfinispan.nodeName": "auth-server-undertow-cross-dc-1.2",
|
||||
"keycloak.connectionsInfinispan.clustered": "${keycloak.connectionsInfinispan.clustered:true}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreServer": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
|
||||
"keycloak.connectionsInfinispan.remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
|
||||
"keycloak.connectionsInfinispan.remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:true}",
|
||||
"keycloak.connectionsJpa.url": "${keycloak.connectionsJpa.url.crossdc:jdbc:h2:mem:test-dc-shared}",
|
||||
"keycloak.connectionsJpa.driver": "${keycloak.connectionsJpa.driver.crossdc:org.h2.Driver}",
|
||||
"keycloak.connectionsJpa.driverDialect": "${keycloak.connectionsJpa.driverDialect.crossdc:}"
|
||||
}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
</group>
|
||||
|
||||
|
||||
<container qualifier="auth-server-balancer-wildfly" mode="suite" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.cluster}</property>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<properties>
|
||||
<auth.server>undertow</auth.server>
|
||||
<auth.server.undertow>true</auth.server.undertow>
|
||||
<auth.server.undertow.crossdc>true</auth.server.undertow.crossdc>
|
||||
|
||||
<auth.server.container>auth-server-${auth.server}</auth.server.container>
|
||||
<auth.server.home>${containers.home}/${auth.server.container}</auth.server.home>
|
||||
|
@ -66,6 +67,16 @@
|
|||
<auth.server.profile/>
|
||||
<auth.server.feature/>
|
||||
|
||||
<cache.server>infinispan</cache.server>
|
||||
<cache.server.container>cache-server-${cache.server}</cache.server.container>
|
||||
<cache.server.home>${containers.home}/${cache.server.container}</cache.server.home>
|
||||
<cache.server.port.offset>1010</cache.server.port.offset>
|
||||
<cache.server.management.port>11000</cache.server.management.port>
|
||||
<cache.server.console.output>true</cache.server.console.output>
|
||||
<keycloak.connectionsInfinispan.remoteStoreServer>localhost</keycloak.connectionsInfinispan.remoteStoreServer>
|
||||
<keycloak.connectionsInfinispan.remoteStorePort>12232</keycloak.connectionsInfinispan.remoteStorePort>
|
||||
<keycloak.connectionsJpa.url.crossdc>jdbc:h2:mem:test-dc-shared</keycloak.connectionsJpa.url.crossdc>
|
||||
|
||||
<adapter.test.props/>
|
||||
<migration.import.properties/>
|
||||
<examples.home>${project.build.directory}/examples</examples.home>
|
||||
|
@ -289,6 +300,7 @@
|
|||
<auth.server>wildfly</auth.server>
|
||||
<auth.server.jboss>true</auth.server.jboss>
|
||||
<auth.server.undertow>false</auth.server.undertow>
|
||||
<auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
|
||||
<auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
|
||||
<auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
|
||||
<h2.version>1.3.173</h2.version>
|
||||
|
@ -307,6 +319,7 @@
|
|||
<auth.server>eap</auth.server>
|
||||
<auth.server.jboss>true</auth.server.jboss>
|
||||
<auth.server.undertow>false</auth.server.undertow>
|
||||
<auth.server.undertow.crossdc>false</auth.server.undertow.crossdc>
|
||||
<auth.server.config.property.value>standalone.xml</auth.server.config.property.value>
|
||||
<auth.server.config.dir>${auth.server.home}/standalone/configuration</auth.server.config.dir>
|
||||
<h2.version>1.3.173</h2.version>
|
||||
|
@ -319,6 +332,110 @@
|
|||
</dependencies>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>cache-server-infinispan</id>
|
||||
<properties>
|
||||
<cache.server>infinispan</cache.server>
|
||||
<cache.server.jboss>true</cache.server.jboss>
|
||||
<cache.server.config.dir>${cache.server.home}/standalone/configuration</cache.server.config.dir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wildfly.arquillian</groupId>
|
||||
<artifactId>wildfly-arquillian-container-managed</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>org.infinispan.arquillian.container</groupId>
|
||||
<artifactId>infinispan-arquillian-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>-->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<!--requireActiveProfile 'auth-server-wildfly/eap' doesn't work unless the profiles are defined in all submodule poms
|
||||
using requireProperty instead-->
|
||||
<requireProperty>
|
||||
<property>cache.server</property>
|
||||
<regex>(infinispan)|(jdg)</regex>
|
||||
<regexMessage>Profile "cache-server-infinispan" requires activation of profile "cache-server-infinispan" or "cache-server-jdg".</regexMessage>
|
||||
</requireProperty>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-cache-server-infinispan</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-servers-cache-server-infinispan</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<outputDirectory>${containers.home}</outputDirectory>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<overWriteIfNewer>true</overWriteIfNewer>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<auth.server.crossdc>true</auth.server.crossdc>
|
||||
<auth.server.undertow.crossdc>${auth.server.undertow.crossdc}</auth.server.undertow.crossdc>
|
||||
|
||||
<cache.server>${cache.server}</cache.server>
|
||||
<cache.server.port.offset>${cache.server.port.offset}</cache.server.port.offset>
|
||||
<cache.server.container>${cache.server.container}</cache.server.container>
|
||||
<cache.server.home>${cache.server.home}</cache.server.home>
|
||||
<cache.server.console.output>${cache.server.console.output}</cache.server.console.output>
|
||||
<cache.server.management.port>${cache.server.management.port}</cache.server.management.port>
|
||||
|
||||
<keycloak.connectionsInfinispan.remoteStorePort>${keycloak.connectionsInfinispan.remoteStorePort}</keycloak.connectionsInfinispan.remoteStorePort>
|
||||
<keycloak.connectionsInfinispan.remoteStoreServer>${keycloak.connectionsInfinispan.remoteStoreServer}</keycloak.connectionsInfinispan.remoteStoreServer>
|
||||
|
||||
<keycloak.connectionsJpa.url.crossdc>${keycloak.connectionsJpa.url.crossdc}</keycloak.connectionsJpa.url.crossdc>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>auth-server-profile</id>
|
||||
<activation>
|
||||
|
|
Loading…
Reference in a new issue