[KEYCLOAK-11328] - Initial Server.x Clustering Configuration

This commit is contained in:
Pedro Igor 2019-11-19 13:05:15 -03:00 committed by Stian Thorgersen
parent f426643225
commit 53f156ec83
16 changed files with 489 additions and 46 deletions

View file

@ -75,6 +75,13 @@
<include>keycloak-runner.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>src/main/content/conf</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>*.*</include>
</includes>
</fileSet>
<fileSet>
<directory>../../</directory>
<outputDirectory></outputDirectory>

View file

@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>8.0.0-SNAPSHOT</version>
<version>9.0.0-SNAPSHOT</version>
</parent>
<artifactId>keycloak-server-x</artifactId>

View file

@ -21,4 +21,4 @@ if [ "x$RESOLVED_NAME" = "x" ]; then
fi
DIRNAME=`dirname "$RESOLVED_NAME"`
java -Dkeycloak.theme.dir=$DIRNAME/../themes -cp "$DIRNAME/../providers/*:$DIRNAME/../lib/keycloak-runner.jar" io.quarkus.runner.GeneratedMain "$@"
java -Dkeycloak.home.dir=$DIRNAME/../ -Dkeycloak.theme.dir=$DIRNAME/../themes -Djava.net.preferIPv4Stack=true -cp "$DIRNAME/../providers/*:$DIRNAME/../lib/keycloak-runner.jar" io.quarkus.runner.GeneratedMain "$@"

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019 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.
-->
<infinispan
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:infinispan:config:9.4 http://www.infinispan.org/schemas/infinispan-config-9.4.xsd"
xmlns="urn:infinispan:config:9.4">
<!-- Distributed Cache Container Configuration-->
<cache-container name="keycloak">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="users">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<distributed-cache name="sessions" owners="1"/>
<distributed-cache name="authenticationSessions" owners="1"/>
<distributed-cache name="offlineSessions" owners="1"/>
<distributed-cache name="clientSessions" owners="1"/>
<distributed-cache name="offlineClientSessions" owners="1"/>
<distributed-cache name="loginFailures" owners="1"/>
<local-cache name="authorization">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<replicated-cache name="work"/>
<local-cache name="keys">
<expiration max-idle="3600000"/>
<memory>
<object size="1000"/>
</memory>
</local-cache>
<distributed-cache name="actionTokens" owners="2">
<expiration max-idle="-1" interval="300000"/>
<memory>
<object size="-1"/>
</memory>
</distributed-cache>
</cache-container>
<!-- Local Cache Container Configuration -->
<!--
<cache-container name="keycloak">
<local-cache name="default">
<transaction transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup"/>
</local-cache>
<local-cache name="realms">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="users">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="sessions"/>
<local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="clientSessions"/>
<local-cache name="offlineClientSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="work"/>
<local-cache name="authorization">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="keys">
<expiration max-idle="3600000"/>
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="actionTokens">
<expiration max-idle="-1" interval="300000"/>
<memory>
<object size="-1"/>
</memory>
</local-cache>
</cache-container>
-->
</infinispan>

View file

@ -17,6 +17,8 @@
package org.keycloak.connections.infinispan;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
import org.infinispan.client.hotrod.ProtocolVersion;
@ -37,11 +39,11 @@ import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup;
import org.jboss.logging.Logger;
import org.jgroups.JChannel;
import org.keycloak.Config;
import org.keycloak.cluster.ManagedCacheManagerProvider;
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.naming.InitialContext;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
/**
@ -98,11 +100,24 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (cacheManager == null) {
synchronized (this) {
if (cacheManager == null) {
String cacheContainer = config.get("cacheContainer");
if (cacheContainer != null) {
initContainerManaged(cacheContainer);
} else {
EmbeddedCacheManager managedCacheManager = null;
Iterator<ManagedCacheManagerProvider> providers = ServiceLoader.load(ManagedCacheManagerProvider.class)
.iterator();
if (providers.hasNext()) {
ManagedCacheManagerProvider provider = providers.next();
if (providers.hasNext()) {
throw new RuntimeException("Multiple " + org.keycloak.cluster.ManagedCacheManagerProvider.class + " providers found.");
}
managedCacheManager = provider.getCacheManager(config);
}
if (managedCacheManager == null) {
initEmbedded();
} else {
initContainerManaged(managedCacheManager);
}
logger.infof(topologyInfo.toString());
@ -113,45 +128,41 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
}
protected void initContainerManaged(String cacheContainerLookup) {
try {
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
protected void initContainerManaged(EmbeddedCacheManager cacheManager) {
this.cacheManager = cacheManager;
containerManaged = true;
long realmRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME).getCacheConfiguration().memory().size();
long realmRevisionsMaxEntries = this.cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME).getCacheConfiguration().memory().size();
realmRevisionsMaxEntries = realmRevisionsMaxEntries > 0
? 2 * realmRevisionsMaxEntries
: InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX;
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, getRevisionCacheConfig(realmRevisionsMaxEntries));
cacheManager.getCache(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, true);
this.cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, getRevisionCacheConfig(realmRevisionsMaxEntries));
this.cacheManager.getCache(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME, true);
long userRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheConfiguration().memory().size();
long userRevisionsMaxEntries = this.cacheManager.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheConfiguration().memory().size();
userRevisionsMaxEntries = userRevisionsMaxEntries > 0
? 2 * userRevisionsMaxEntries
: InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX;
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(userRevisionsMaxEntries));
cacheManager.getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
this.cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(userRevisionsMaxEntries));
this.cacheManager.getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, true);
this.cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, true);
this.cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);
this.cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
this.cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
long authzRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME).getCacheConfiguration().memory().size();
long authzRevisionsMaxEntries = this.cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME).getCacheConfiguration().memory().size();
authzRevisionsMaxEntries = authzRevisionsMaxEntries > 0
? 2 * authzRevisionsMaxEntries
: InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_DEFAULT_MAX;
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME, getRevisionCacheConfig(authzRevisionsMaxEntries));
cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME, true);
this.cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME, getRevisionCacheConfig(authzRevisionsMaxEntries));
this.cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME, true);
this.topologyInfo = new TopologyInfo(cacheManager, config, false);
this.topologyInfo = new TopologyInfo(this.cacheManager, config, false);
logger.debugv("Using container managed Infinispan cache container, lookup={0}", cacheContainerLookup);
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve cache container", e);
}
logger.debugv("Using container managed Infinispan cache container, lookup={0}", cacheManager);
}
protected void initEmbedded() {

View file

@ -36,6 +36,14 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>

View file

@ -0,0 +1,101 @@
/*
* Copyright 2019 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.provider.quarkus;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
import org.infinispan.configuration.parsing.ParserRegistry;
import org.infinispan.manager.DefaultCacheManager;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.cluster.ManagedCacheManagerProvider;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class QuarkusCacheManagerProvider implements ManagedCacheManagerProvider {
private static final Logger log = Logger.getLogger(QuarkusCacheManagerProvider.class);
private static final String DEFAULT_CONFIGURATION_FILE_NAME = "cluster.xml";
@Override
public <C> C getCacheManager(Config.Scope config) {
try {
InputStream configurationStream = loadConfiguration(config);
ConfigurationBuilderHolder builder = new ParserRegistry().parse(configurationStream);
configureTransportStack(config, builder);
return (C) new DefaultCacheManager(builder, true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private InputStream loadConfiguration(Config.Scope config) throws FileNotFoundException {
String homeDir = System.getProperty("keycloak.home.dir");
if (homeDir == null) {
log.warn("Keycloak home directory not set.");
return loadDefaultConfiguration(config);
}
Path configPath = Paths.get(homeDir + "/conf/" + getConfigFileName(config));
if (configPath.toFile().exists()) {
log.debugf("Loading cluster configuration from %s", configPath);
return FileLookupFactory.newInstance()
.lookupFileStrict(configPath.toUri(), Thread.currentThread().getContextClassLoader());
}
log.infof("Clustering configuration file not found at %s.", configPath);
return loadDefaultConfiguration(config);
}
private InputStream loadDefaultConfiguration(Config.Scope config) throws FileNotFoundException {
if (config.getBoolean("clustered")) {
log.debugf("Using default clustered cache configuration.");
return FileLookupFactory.newInstance()
.lookupFileStrict("default-clustered-cache.xml", Thread.currentThread().getContextClassLoader());
}
log.debug("Using default local cache configuration.");
return FileLookupFactory.newInstance()
.lookupFileStrict("default-local-cache.xml", Thread.currentThread().getContextClassLoader());
}
private String getConfigFileName(Config.Scope config) {
String configFile = config.get("configFile");
return configFile == null ? DEFAULT_CONFIGURATION_FILE_NAME : configFile;
}
private void configureTransportStack(Config.Scope config, ConfigurationBuilderHolder builder) {
String transportStack = config.get("stack");
if (transportStack != null) {
builder.getGlobalConfigurationBuilder().transport().defaultTransport()
.addProperty("configurationFile", "default-configs/default-jgroups-" + transportStack + ".xml");
}
}
}

View file

@ -0,0 +1 @@
org.keycloak.provider.quarkus.QuarkusCacheManagerProvider

View file

@ -316,7 +316,6 @@
</exclusion>
</exclusions>
</dependency>
<!--TODO Should come from Infinispan extension -->
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>

View file

@ -105,7 +105,8 @@
"remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}",
"remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
"remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
"hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}"
"hotrodProtocolVersion": "${keycloak.connectionsInfinispan.hotrodProtocolVersion}",
"stack": "${keycloak.connectionsInfinispan.stack:udp}"
}
},

View file

@ -67,10 +67,4 @@
<filter-name>Keycloak Session Management</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<resource-env-ref>
<resource-env-ref-name>infinispan/Keycloak</resource-env-ref-name>
<resource-env-ref-type>org.infinispan.manager.EmbeddedCacheManager</resource-env-ref-type>
<lookup-name>java:jboss/infinispan/container/keycloak</lookup-name>
</resource-env-ref>
</web-app>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019 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.
-->
<infinispan
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:infinispan:config:9.4 http://www.infinispan.org/schemas/infinispan-config-9.4.xsd"
xmlns="urn:infinispan:config:9.4">
<cache-container name="keycloak">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="users">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<distributed-cache name="sessions" owners="1"/>
<distributed-cache name="authenticationSessions" owners="1"/>
<distributed-cache name="offlineSessions" owners="1"/>
<distributed-cache name="clientSessions" owners="1"/>
<distributed-cache name="offlineClientSessions" owners="1"/>
<distributed-cache name="loginFailures" owners="1"/>
<local-cache name="authorization">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<replicated-cache name="work"/>
<local-cache name="keys">
<expiration max-idle="3600000"/>
<memory>
<object size="1000"/>
</memory>
</local-cache>
<distributed-cache name="actionTokens" owners="2">
<expiration max-idle="-1" interval="300000"/>
<memory>
<object size="-1"/>
</memory>
</distributed-cache>
</cache-container>
</infinispan>

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019 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.
-->
<infinispan
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:infinispan:config:9.4 http://www.infinispan.org/schemas/infinispan-config-9.4.xsd"
xmlns="urn:infinispan:config:9.4">
<cache-container name="keycloak">
<local-cache name="default">
<transaction transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup"/>
</local-cache>
<local-cache name="realms">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="users">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="sessions"/>
<local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="clientSessions"/>
<local-cache name="offlineClientSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="work"/>
<local-cache name="authorization">
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="keys">
<expiration max-idle="3600000"/>
<memory>
<object size="10000"/>
</memory>
</local-cache>
<local-cache name="actionTokens">
<expiration max-idle="-1" interval="300000"/>
<memory>
<object size="-1"/>
</memory>
</local-cache>
</cache-container>
</infinispan>

View file

@ -0,0 +1,30 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.cluster;
import org.keycloak.Config;
/**
* A Service Provider Interface (SPI) that allows to plug-in a cache manager instance.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ManagedCacheManagerProvider {
<C> C getCacheManager(Config.Scope config);
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2019 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.provider.wildfly;
import javax.naming.InitialContext;
import org.keycloak.Config;
import org.keycloak.cluster.ManagedCacheManagerProvider;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class WildflyCacheManagerProvider implements ManagedCacheManagerProvider {
@Override
public <C> C getCacheManager(Config.Scope config) {
String cacheContainer = config.get("cacheContainer");
if (cacheContainer == null) {
return null;
}
try {
return (C) new InitialContext().lookup(cacheContainer);
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve cache container", e);
}
}
}

View file

@ -0,0 +1,18 @@
#
# Copyright 2019 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.
#
org.keycloak.provider.wildfly.WildflyCacheManagerProvider