diff --git a/distribution/server-x/assembly.xml b/distribution/server-x/assembly.xml index fe6952edbe..2ce052bd13 100755 --- a/distribution/server-x/assembly.xml +++ b/distribution/server-x/assembly.xml @@ -75,6 +75,13 @@ keycloak-runner.jar + + src/main/content/conf + conf + + *.* + + ../../ diff --git a/distribution/server-x/pom.xml b/distribution/server-x/pom.xml index 6d5ec97711..b982df8588 100755 --- a/distribution/server-x/pom.xml +++ b/distribution/server-x/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 8.0.0-SNAPSHOT + 9.0.0-SNAPSHOT keycloak-server-x diff --git a/distribution/server-x/src/main/content/bin/kc.sh b/distribution/server-x/src/main/content/bin/kc.sh index 52560a3f57..919efa24b4 100644 --- a/distribution/server-x/src/main/content/bin/kc.sh +++ b/distribution/server-x/src/main/content/bin/kc.sh @@ -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 "$@" diff --git a/distribution/server-x/src/main/content/conf/cluster.xml b/distribution/server-x/src/main/content/conf/cluster.xml new file mode 100644 index 0000000000..870142877f --- /dev/null +++ b/distribution/server-x/src/main/content/conf/cluster.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index 467b03fa3e..00f54d2968 100755 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -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 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); - containerManaged = true; + protected void initContainerManaged(EmbeddedCacheManager cacheManager) { + this.cacheManager = cacheManager; + containerManaged = true; - long realmRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME).getCacheConfiguration().memory().size(); - realmRevisionsMaxEntries = realmRevisionsMaxEntries > 0 - ? 2 * realmRevisionsMaxEntries - : InfinispanConnectionProvider.REALM_REVISIONS_CACHE_DEFAULT_MAX; + 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(); - userRevisionsMaxEntries = userRevisionsMaxEntries > 0 - ? 2 * userRevisionsMaxEntries - : InfinispanConnectionProvider.USER_REVISIONS_CACHE_DEFAULT_MAX; + 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(); - authzRevisionsMaxEntries = authzRevisionsMaxEntries > 0 - ? 2 * authzRevisionsMaxEntries - : InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_DEFAULT_MAX; + 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() { diff --git a/quarkus/extensions/pom.xml b/quarkus/extensions/pom.xml index 4bab3990d6..16516b173b 100644 --- a/quarkus/extensions/pom.xml +++ b/quarkus/extensions/pom.xml @@ -36,6 +36,14 @@ org.keycloak keycloak-services + + org.keycloak + keycloak-server-spi-private + + + org.infinispan + infinispan-core + io.quarkus quarkus-arc diff --git a/quarkus/extensions/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java b/quarkus/extensions/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java new file mode 100644 index 0000000000..5c0acbd357 --- /dev/null +++ b/quarkus/extensions/src/main/java/org/keycloak/provider/quarkus/QuarkusCacheManagerProvider.java @@ -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 Pedro Igor + */ +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 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"); + } + } +} diff --git a/quarkus/extensions/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider b/quarkus/extensions/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider new file mode 100644 index 0000000000..9365509a2b --- /dev/null +++ b/quarkus/extensions/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider @@ -0,0 +1 @@ +org.keycloak.provider.quarkus.QuarkusCacheManagerProvider \ No newline at end of file diff --git a/quarkus/server/pom.xml b/quarkus/server/pom.xml index adea690df8..9e76e2bce7 100644 --- a/quarkus/server/pom.xml +++ b/quarkus/server/pom.xml @@ -316,7 +316,6 @@ - org.infinispan infinispan-core diff --git a/quarkus/server/src/main/resources/META-INF/keycloak-server.json b/quarkus/server/src/main/resources/META-INF/keycloak-server.json index 5ed3ac19af..9abad69c8b 100755 --- a/quarkus/server/src/main/resources/META-INF/keycloak-server.json +++ b/quarkus/server/src/main/resources/META-INF/keycloak-server.json @@ -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}" } }, diff --git a/quarkus/server/src/main/resources/META-INF/web.xml b/quarkus/server/src/main/resources/META-INF/web.xml index 26a88ff94f..09fe42b57b 100644 --- a/quarkus/server/src/main/resources/META-INF/web.xml +++ b/quarkus/server/src/main/resources/META-INF/web.xml @@ -67,10 +67,4 @@ Keycloak Session Management /* - - - infinispan/Keycloak - org.infinispan.manager.EmbeddedCacheManager - java:jboss/infinispan/container/keycloak - diff --git a/quarkus/server/src/main/resources/default-clustered-cache.xml b/quarkus/server/src/main/resources/default-clustered-cache.xml new file mode 100644 index 0000000000..17f9484084 --- /dev/null +++ b/quarkus/server/src/main/resources/default-clustered-cache.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quarkus/server/src/main/resources/default-local-cache.xml b/quarkus/server/src/main/resources/default-local-cache.xml new file mode 100644 index 0000000000..fd2a4b45f4 --- /dev/null +++ b/quarkus/server/src/main/resources/default-local-cache.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server-spi-private/src/main/java/org/keycloak/cluster/ManagedCacheManagerProvider.java b/server-spi-private/src/main/java/org/keycloak/cluster/ManagedCacheManagerProvider.java new file mode 100644 index 0000000000..2f32bf3476 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/cluster/ManagedCacheManagerProvider.java @@ -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 Pedro Igor + */ +public interface ManagedCacheManagerProvider { + + C getCacheManager(Config.Scope config); +} diff --git a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyCacheManagerProvider.java b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyCacheManagerProvider.java new file mode 100644 index 0000000000..11de009cc2 --- /dev/null +++ b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyCacheManagerProvider.java @@ -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 Pedro Igor + */ +public class WildflyCacheManagerProvider implements ManagedCacheManagerProvider { + + @Override + public 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); + } + } +} diff --git a/wildfly/extensions/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider b/wildfly/extensions/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider new file mode 100644 index 0000000000..a1836f87ab --- /dev/null +++ b/wildfly/extensions/src/main/resources/META-INF/services/org.keycloak.cluster.ManagedCacheManagerProvider @@ -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 \ No newline at end of file