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
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]}
find . -path '*/target/surefire-reports/*.xml' | zip -q reports-quarkus-tests.zip -@
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))
.forEach(administration::reindexCache);
}
LOG.infof("HotRod client configuration was successful.");
}
private void registerSchemata() {

View file

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

View file

@ -17,17 +17,22 @@
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.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StorageOptions {
public enum StorageType {
jpa("jpa"),
chm("concurrenthashmap");
chm("concurrenthashmap"),
hotrod("hotrod");
private final String provider;
@ -285,5 +290,53 @@ public class StorageOptions {
.buildTime(true)
.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>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map-hot-rod</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Keycloak Dependencies-->
<dependency>
@ -437,6 +447,50 @@
</exclusion>
</exclusions>
</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>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-cachestore-remote</artifactId>

View file

@ -270,6 +270,30 @@ final class StoragePropertyMappers {
.mapFrom("storage")
.transformer(StoragePropertyMappers::isLegacyStoreEnabled)
.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()
};
}

View file

@ -21,6 +21,7 @@
quarkus.log.min-level=TRACE
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.client.hotrod.impl.query.RemoteQuery".level=ERROR
#jndi needed for LDAP lookups
quarkus.naming.enable-jndi=true

View file

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

View file

@ -18,30 +18,28 @@
package org.keycloak.it.junit5.extension;
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.MariaDBContainer;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.DockerImageName;
public class DatabaseContainer {
static final String DEFAULT_PASSWORD = "Password1!";
private final String alias;
private JdbcDatabaseContainer container;
private GenericContainer<?> container;
DatabaseContainer(String alias) {
this.alias = alias;
}
void start() {
container = createContainer()
.withDatabaseName("keycloak")
.withUsername(getUsername())
.withPassword(getPassword())
.withInitScript(resolveInitScript());
container = createContainer();
container.withStartupTimeout(Duration.ofMinutes(5)).start();
}
@ -49,8 +47,22 @@ public class DatabaseContainer {
return container.isRunning();
}
String getJdbcUrl() {
return container.getJdbcUrl();
void configureDistribution(KeycloakDistribution dist) {
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() {
@ -66,8 +78,21 @@ public class DatabaseContainer {
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 MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image", "mariadb:10.5.9");
@ -76,9 +101,12 @@ public class DatabaseContainer {
switch (alias) {
case "postgres":
return new PostgreSQLContainer(POSTGRES);
return configureJdbcContainer(new PostgreSQLContainer(POSTGRES));
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:
throw new RuntimeException("Unsupported database: " + alias);
}

View file

@ -72,7 +72,7 @@ public class HelpCommandTest {
void testStartOptimizedHelpAll(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertHelp();
cliResult.assertNoMessage("--storage");
cliResult.assertNoMessage("--storage ");
}
@Test

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 <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:

View file

@ -29,7 +29,21 @@ Cache:
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:
@ -203,4 +217,4 @@ Logging:
Do NOT start the server using this command when deploying to production.
Use 'kc.bat start-dev --help-all' to list all available options, including
build options.
build options.

View file

@ -35,7 +35,21 @@ Cache:
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:

View file

@ -35,7 +35,21 @@ Cache:
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:
@ -213,4 +227,4 @@ By default, this command tries to update the server configuration by running a
$ kc.bat start '--optimized'
By doing that, the server should start faster based on any previous
configuration you have set when manually running the 'build' command.
configuration you have set when manually running the 'build' command.

View file

@ -20,6 +20,23 @@ Options:
--optimized Use this option to achieve an optional startup time if you have previously
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:
--db-password <password>

View file

@ -20,6 +20,23 @@ Options:
--optimized Use this option to achieve an optional startup time if you have previously
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:
--db-password <password>
@ -166,4 +183,4 @@ By default, this command tries to update the server configuration by running a
$ kc.bat start '--optimized'
By doing that, the server should start faster based on any previous
configuration you have set when manually running the 'build' command.
configuration you have set when manually running the 'build' command.