Add possibility to configure HotRod storage in Quarkus distribution

Closes #12617
This commit is contained in:
Michal Hajas 2022-07-25 23:34:35 +02:00 committed by Hynek Mlnařík
parent 865a180c00
commit 3589778a10
17 changed files with 326 additions and 27 deletions

View file

@ -492,7 +492,7 @@ jobs:
- name: Run Quarkus Storage Tests - name: Run Quarkus Storage Tests
run: | run: |
./mvnw clean install -nsu -B -f quarkus/tests/pom.xml -Ptest-database -Dtest=PostgreSQLDistTest,DatabaseOptionsDistTest,JPAStoreDistTest | misc/log/trimmer.sh ./mvnw clean install -nsu -B -f quarkus/tests/pom.xml -Ptest-database -Dtest=PostgreSQLDistTest,DatabaseOptionsDistTest,JPAStoreDistTest,HotRodStoreDistTest | misc/log/trimmer.sh
TEST_RESULT=${PIPESTATUS[0]} TEST_RESULT=${PIPESTATUS[0]}
find . -path '*/target/surefire-reports/*.xml' | zip -q reports-quarkus-tests.zip -@ find . -path '*/target/surefire-reports/*.xml' | zip -q reports-quarkus-tests.zip -@
exit $TEST_RESULT exit $TEST_RESULT

View file

@ -137,6 +137,8 @@ public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionP
.peek(cacheName -> LOG.infof("Reindexing %s cache. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.", cacheName)) .peek(cacheName -> LOG.infof("Reindexing %s cache. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.", cacheName))
.forEach(administration::reindexCache); .forEach(administration::reindexCache);
} }
LOG.infof("HotRod client configuration was successful.");
} }
private void registerSchemata() { private void registerSchemata() {

View file

@ -36,6 +36,16 @@
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId> <artifactId>keycloak-common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map-hot-rod</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
</project> </project>

View file

@ -17,17 +17,22 @@
package org.keycloak.config; package org.keycloak.config;
import org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StorageOptions { public class StorageOptions {
public enum StorageType { public enum StorageType {
jpa("jpa"), jpa("jpa"),
chm("concurrenthashmap"); chm("concurrenthashmap"),
hotrod("hotrod");
private final String provider; private final String provider;
@ -285,5 +290,53 @@ public class StorageOptions {
.buildTime(true) .buildTime(true)
.build(); .build();
public static final List<Option<?>> ALL_OPTIONS = List.of(STORAGE); public static final Option<String> STORAGE_HOTROD_HOST = new OptionBuilder<>("storage-hotrod-host", String.class)
.category(OptionCategory.STORAGE)
.description("Sets the host of the Infinispan server.")
.hidden()
.build();
public static final Option<Integer> STORAGE_HOTROD_PORT = new OptionBuilder<>("storage-hotrod-port", Integer.class)
.category(OptionCategory.STORAGE)
.description("Sets the port of the Infinispan server.")
.hidden()
.build();
public static final Option<String> STORAGE_HOTROD_USERNAME = new OptionBuilder<>("storage-hotrod-username", String.class)
.category(OptionCategory.STORAGE)
.description("Sets the username of the Infinispan user.")
.hidden()
.build();
public static final Option<String> STORAGE_HOTROD_PASSWORD = new OptionBuilder<>("storage-hotrod-password", String.class)
.category(OptionCategory.STORAGE)
.description("Sets the password of the Infinispan user.")
.hidden()
.build();
public static final Option<Boolean> STORAGE_HOTROD_CACHE_CONFIGURE = new OptionBuilder<>("storage-hotrod-cache-configure", Boolean.class)
.category(OptionCategory.STORAGE)
.defaultValue(true)
.description("When set to true, Keycloak will create and configure Infinispan caches on startup.")
.hidden()
.build();
public static final Option<String> STORAGE_HOTROD_CACHE_REINDEX = new OptionBuilder<>("storage-hotrod-cache-reindex", String.class)
.category(OptionCategory.STORAGE)
.defaultValue("all")
.expectedValues(Stream.concat(Stream.of("all"), AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getCacheName).distinct()).collect(Collectors.toList()))
.description("List of cache names that should be indexed on Keycloak startup. Defaulting to `all` which means all caches are reindexed.")
.hidden()
.build();
public static final List<Option<?>> ALL_OPTIONS = List.of(
STORAGE,
STORAGE_HOTROD_HOST,
STORAGE_HOTROD_PORT,
STORAGE_HOTROD_USERNAME,
STORAGE_HOTROD_PASSWORD,
STORAGE_HOTROD_CACHE_CONFIGURE,
STORAGE_HOTROD_CACHE_REINDEX
);
} }

View file

@ -320,6 +320,16 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map-hot-rod</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Keycloak Dependencies--> <!-- Keycloak Dependencies-->
<dependency> <dependency>
@ -437,6 +447,50 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query-dsl</artifactId>
<version>${infinispan.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId>
<version>${infinispan.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-rest</artifactId>
<version>${infinispan.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-router</artifactId>
<version>${infinispan.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.infinispan</groupId> <groupId>org.infinispan</groupId>
<artifactId>infinispan-cachestore-remote</artifactId> <artifactId>infinispan-cachestore-remote</artifactId>

View file

@ -270,6 +270,30 @@ final class StoragePropertyMappers {
.mapFrom("storage") .mapFrom("storage")
.transformer(StoragePropertyMappers::isLegacyStoreEnabled) .transformer(StoragePropertyMappers::isLegacyStoreEnabled)
.paramLabel("type") .paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_HOTROD_HOST)
.to("kc.spi-connections-hot-rod-default-host")
.paramLabel("host")
.build(),
fromOption(StorageOptions.STORAGE_HOTROD_PORT)
.to("kc.spi-connections-hot-rod-default-port")
.paramLabel("port")
.build(),
fromOption(StorageOptions.STORAGE_HOTROD_USERNAME)
.to("kc.spi-connections-hot-rod-default-username")
.paramLabel("username")
.build(),
fromOption(StorageOptions.STORAGE_HOTROD_PASSWORD)
.to("kc.spi-connections-hot-rod-default-password")
.paramLabel("password")
.build(),
fromOption(StorageOptions.STORAGE_HOTROD_CACHE_CONFIGURE)
.to("kc.spi-connections-hot-rod-default-configure-remote-caches")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.build(),
fromOption(StorageOptions.STORAGE_HOTROD_CACHE_REINDEX)
.to("kc.spi-connections-hot-rod-default-reindex-caches")
.paramLabel("[cache1,cache2,...]|all")
.build() .build()
}; };
} }

View file

@ -21,6 +21,7 @@
quarkus.log.min-level=TRACE quarkus.log.min-level=TRACE
quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN quarkus.log.category."org.jboss.resteasy.resteasy_jaxrs.i18n".level=WARN
quarkus.log.category."org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup".level=WARN quarkus.log.category."org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup".level=WARN
quarkus.log.category."org.infinispan.client.hotrod.impl.query.RemoteQuery".level=ERROR
#jndi needed for LDAP lookups #jndi needed for LDAP lookups
quarkus.naming.enable-jndi=true quarkus.naming.enable-jndi=true

View file

@ -263,9 +263,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
} }
} }
dist.setProperty("db-username", databaseContainer.getUsername()); databaseContainer.configureDistribution(dist);
dist.setProperty("db-password", databaseContainer.getPassword());
dist.setProperty("db-url", databaseContainer.getJdbcUrl());
dist.run("build"); dist.run("build");
} }

View file

@ -18,30 +18,28 @@
package org.keycloak.it.junit5.extension; package org.keycloak.it.junit5.extension;
import java.time.Duration; import java.time.Duration;
import org.jetbrains.annotations.NotNull;
import org.keycloak.it.utils.KeycloakDistribution;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.containers.MariaDBContainer;
import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.DockerImageName;
public class DatabaseContainer { public class DatabaseContainer {
static final String DEFAULT_PASSWORD = "Password1!"; static final String DEFAULT_PASSWORD = "Password1!";
private final String alias; private final String alias;
private JdbcDatabaseContainer container; private GenericContainer<?> container;
DatabaseContainer(String alias) { DatabaseContainer(String alias) {
this.alias = alias; this.alias = alias;
} }
void start() { void start() {
container = createContainer() container = createContainer();
.withDatabaseName("keycloak")
.withUsername(getUsername())
.withPassword(getPassword())
.withInitScript(resolveInitScript());
container.withStartupTimeout(Duration.ofMinutes(5)).start(); container.withStartupTimeout(Duration.ofMinutes(5)).start();
} }
@ -49,8 +47,22 @@ public class DatabaseContainer {
return container.isRunning(); return container.isRunning();
} }
String getJdbcUrl() { void configureDistribution(KeycloakDistribution dist) {
return container.getJdbcUrl(); if (alias.equals("infinispan")) {
dist.setProperty("storage-hotrod-username", getUsername());
dist.setProperty("storage-hotrod-password", getPassword());
dist.setProperty("storage-hotrod-host", container.getContainerIpAddress());
dist.setProperty("storage-hotrod-port", String.valueOf(container.getMappedPort(11222)));
} else {
dist.setProperty("db-username", getUsername());
dist.setProperty("db-password", getPassword());
dist.setProperty("db-url", getJdbcUrl());
}
}
private String getJdbcUrl() {
return ((JdbcDatabaseContainer)container).getJdbcUrl();
} }
String getUsername() { String getUsername() {
@ -66,8 +78,21 @@ public class DatabaseContainer {
container = null; container = null;
} }
private JdbcDatabaseContainer createContainer() { private GenericContainer<?> configureJdbcContainer(JdbcDatabaseContainer jdbcDatabaseContainer) {
return jdbcDatabaseContainer
.withDatabaseName("keycloak")
.withUsername(getUsername())
.withPassword(getPassword())
.withInitScript(resolveInitScript());
}
private GenericContainer<?> configureInfinispanUser(GenericContainer<?> infinispanContainer) {
infinispanContainer.addEnv("USER", getUsername());
infinispanContainer.addEnv("PASS", getPassword());
return infinispanContainer;
}
private GenericContainer<?> createContainer() {
String POSTGRES_IMAGE = System.getProperty("kc.db.postgresql.container.image", "postgres:alpine"); String POSTGRES_IMAGE = System.getProperty("kc.db.postgresql.container.image", "postgres:alpine");
String MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image", "mariadb:10.5.9"); String MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image", "mariadb:10.5.9");
@ -76,9 +101,12 @@ public class DatabaseContainer {
switch (alias) { switch (alias) {
case "postgres": case "postgres":
return new PostgreSQLContainer(POSTGRES); return configureJdbcContainer(new PostgreSQLContainer(POSTGRES));
case "mariadb": case "mariadb":
return new MariaDBContainer(MARIADB); return configureJdbcContainer(new MariaDBContainer(MARIADB));
case "infinispan":
return configureInfinispanUser(new GenericContainer("quay.io/infinispan/server:12.1.7.Final"))
.withExposedPorts(11222);
default: default:
throw new RuntimeException("Unsupported database: " + alias); throw new RuntimeException("Unsupported database: " + alias);
} }

View file

@ -0,0 +1,39 @@
/*
* Copyright 2022 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.it.storage.map;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.WithDatabase;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@WithDatabase(alias = "infinispan", buildOptions={"storage=hotrod"})
public class HotRodStoreDistTest {
@Test
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
void testSuccessful(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Experimental feature enabled: map_storage");
cliResult.assertMessage("[org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory] (main) HotRod client configuration was successful.");
cliResult.assertStarted();
}
}

View file

@ -29,7 +29,21 @@ Cache:
Storage (Experimental): Storage (Experimental):
--storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm. --storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm, hotrod.
--storage-hotrod-cache-configure <true|false>
Experimental: When set to true, Keycloak will create and configure Infinispan
caches on startup. Default: true.
--storage-hotrod-cache-reindex <[cache1,cache2,...]|all>
Experimental: List of cache names that should be indexed on Keycloak startup.
Defaulting to `all` which means all caches are reindexed. Default: all.
--storage-hotrod-host <host>
Experimental: Sets the host of the Infinispan server.
--storage-hotrod-password <password>
Experimental: Sets the password of the Infinispan user.
--storage-hotrod-port <port>
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user.
Database: Database:

View file

@ -29,7 +29,21 @@ Cache:
Storage (Experimental): Storage (Experimental):
--storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm. --storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm, hotrod.
--storage-hotrod-cache-configure <true|false>
Experimental: When set to true, Keycloak will create and configure Infinispan
caches on startup. Default: true.
--storage-hotrod-cache-reindex <[cache1,cache2,...]|all>
Experimental: List of cache names that should be indexed on Keycloak startup.
Defaulting to `all` which means all caches are reindexed. Default: all.
--storage-hotrod-host <host>
Experimental: Sets the host of the Infinispan server.
--storage-hotrod-password <password>
Experimental: Sets the password of the Infinispan user.
--storage-hotrod-port <port>
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user.
Database: Database:

View file

@ -35,7 +35,21 @@ Cache:
Storage (Experimental): Storage (Experimental):
--storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm. --storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm, hotrod.
--storage-hotrod-cache-configure <true|false>
Experimental: When set to true, Keycloak will create and configure Infinispan
caches on startup. Default: true.
--storage-hotrod-cache-reindex <[cache1,cache2,...]|all>
Experimental: List of cache names that should be indexed on Keycloak startup.
Defaulting to `all` which means all caches are reindexed. Default: all.
--storage-hotrod-host <host>
Experimental: Sets the host of the Infinispan server.
--storage-hotrod-password <password>
Experimental: Sets the password of the Infinispan user.
--storage-hotrod-port <port>
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user.
Database: Database:

View file

@ -35,7 +35,21 @@ Cache:
Storage (Experimental): Storage (Experimental):
--storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm. --storage <type> Experimental: Sets a storage mechanism. Possible values are: jpa, chm, hotrod.
--storage-hotrod-cache-configure <true|false>
Experimental: When set to true, Keycloak will create and configure Infinispan
caches on startup. Default: true.
--storage-hotrod-cache-reindex <[cache1,cache2,...]|all>
Experimental: List of cache names that should be indexed on Keycloak startup.
Defaulting to `all` which means all caches are reindexed. Default: all.
--storage-hotrod-host <host>
Experimental: Sets the host of the Infinispan server.
--storage-hotrod-password <password>
Experimental: Sets the password of the Infinispan user.
--storage-hotrod-port <port>
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user.
Database: Database:

View file

@ -20,6 +20,23 @@ Options:
--optimized Use this option to achieve an optional startup time if you have previously --optimized Use this option to achieve an optional startup time if you have previously
built a server image using the 'build' command. built a server image using the 'build' command.
Storage (Experimental):
--storage-hotrod-cache-configure <true|false>
Experimental: When set to true, Keycloak will create and configure Infinispan
caches on startup. Default: true.
--storage-hotrod-cache-reindex <[cache1,cache2,...]|all>
Experimental: List of cache names that should be indexed on Keycloak startup.
Defaulting to `all` which means all caches are reindexed. Default: all.
--storage-hotrod-host <host>
Experimental: Sets the host of the Infinispan server.
--storage-hotrod-password <password>
Experimental: Sets the password of the Infinispan user.
--storage-hotrod-port <port>
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user.
Database: Database:
--db-password <password> --db-password <password>

View file

@ -20,6 +20,23 @@ Options:
--optimized Use this option to achieve an optional startup time if you have previously --optimized Use this option to achieve an optional startup time if you have previously
built a server image using the 'build' command. built a server image using the 'build' command.
Storage (Experimental):
--storage-hotrod-cache-configure <true|false>
Experimental: When set to true, Keycloak will create and configure Infinispan
caches on startup. Default: true.
--storage-hotrod-cache-reindex <[cache1,cache2,...]|all>
Experimental: List of cache names that should be indexed on Keycloak startup.
Defaulting to `all` which means all caches are reindexed. Default: all.
--storage-hotrod-host <host>
Experimental: Sets the host of the Infinispan server.
--storage-hotrod-password <password>
Experimental: Sets the password of the Infinispan user.
--storage-hotrod-port <port>
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user.
Database: Database:
--db-password <password> --db-password <password>