Remove possibility to start embedded HotRod server in hotrod-map module

Closes #13247
This commit is contained in:
Michal Hajas 2022-08-02 15:49:17 +02:00 committed by Hynek Mlnařík
parent cf249fc6ae
commit ec808d28bb
13 changed files with 163 additions and 140 deletions

View file

@ -30,18 +30,6 @@
<groupId>org.infinispan</groupId> <groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId> <artifactId>infinispan-remote-query-client</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-router</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-rest</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-server-runtime</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.infinispan.protostream</groupId> <groupId>org.infinispan.protostream</groupId>
<artifactId>protostream-processor</artifactId> <artifactId>protostream-processor</artifactId>

View file

@ -16,26 +16,7 @@
*/ */
package org.keycloak.models.map.storage.hotRod.common; package org.keycloak.models.map.storage.hotRod.common;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.query.dsl.Query; import org.infinispan.query.dsl.Query;
import org.infinispan.rest.RestServer;
import org.infinispan.rest.configuration.RestServerConfigurationBuilder;
import org.infinispan.server.configuration.endpoint.SinglePortServerConfigurationBuilder;
import org.infinispan.server.hotrod.HotRodServer;
import org.infinispan.server.hotrod.configuration.HotRodServerConfigurationBuilder;
import org.infinispan.server.router.RoutingTable;
import org.infinispan.server.router.configuration.SinglePortRouterConfiguration;
import org.infinispan.server.router.router.impl.singleport.SinglePortEndpointRouter;
import org.infinispan.server.router.routes.Route;
import org.infinispan.server.router.routes.RouteDestination;
import org.infinispan.server.router.routes.RouteSource;
import org.infinispan.server.router.routes.hotrod.HotRodServerRouteDestination;
import org.infinispan.server.router.routes.rest.RestServerRouteDestination;
import org.infinispan.server.router.routes.singleport.SinglePortRouteSource;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/** /**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a> * @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
@ -44,51 +25,6 @@ public class HotRodUtils {
public static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE >> 1; public static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE >> 1;
/**
* Not suitable for a production usage. Only for development and test purposes.
* Also do not use in clustered environment.
* @param hotRodServer HotRodServer
* @param hotRodCacheManager DefaultCacheManager
* @param embeddedPort int
*/
public static void createHotRodMapStoreServer(HotRodServer hotRodServer, DefaultCacheManager hotRodCacheManager, int embeddedPort) {
HotRodServerConfigurationBuilder hotRodServerConfigurationBuilder = new HotRodServerConfigurationBuilder();
hotRodServerConfigurationBuilder.startTransport(false);
hotRodServerConfigurationBuilder.port(embeddedPort);
hotRodServer.start(hotRodServerConfigurationBuilder.build(), hotRodCacheManager);
RestServerConfigurationBuilder restServerConfigurationBuilder = new RestServerConfigurationBuilder();
restServerConfigurationBuilder.startTransport(false);
restServerConfigurationBuilder.port(embeddedPort);
RestServer restServer = new RestServer();
restServer.start(restServerConfigurationBuilder.build(), hotRodCacheManager);
SinglePortRouteSource routeSource = new SinglePortRouteSource();
Set<Route<? extends RouteSource, ? extends RouteDestination>> routes = new HashSet<>();
routes.add(new Route<>(routeSource, new HotRodServerRouteDestination("hotrod", hotRodServer)));
routes.add(new Route<>(routeSource, new RestServerRouteDestination("rest", restServer)));
SinglePortRouterConfiguration singlePortRouter = new SinglePortServerConfigurationBuilder().port(embeddedPort).build();
SinglePortEndpointRouter endpointServer = new SinglePortEndpointRouter(singlePortRouter);
endpointServer.start(new RoutingTable(routes));
}
/**
* Not suitable for a production usage. Only for development and test purposes.
* Also do not use in clustered environment.
* @param embeddedPort int
*/
public static void createHotRodMapStoreServer(int embeddedPort) {
DefaultCacheManager hotRodCacheManager = null;
try {
hotRodCacheManager = new DefaultCacheManager("config/infinispan.xml");
} catch (IOException e) {
new RuntimeException("Cannot initialize cache manager!", e);
}
HotRodUtils.createHotRodMapStoreServer(new HotRodServer(), hotRodCacheManager, embeddedPort);
}
public static <T> Query<T> paginateQuery(Query<T> query, Integer first, Integer max) { public static <T> Query<T> paginateQuery(Query<T> query, Integer first, Integer max) {
if (first != null && first > 0) { if (first != null && first > 0) {
query = query.startOffset(first); query = query.startOffset(first);

View file

@ -30,7 +30,6 @@ import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor; import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.storage.hotRod.common.HotRodUtils;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer; import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils; import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils;
@ -89,14 +88,10 @@ public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionP
} }
public void lazyInit() { public void lazyInit() {
if (config.getBoolean("embedded", false)) {
HotRodUtils.createHotRodMapStoreServer(config.getInt("embeddedPort", 11444));
}
ConfigurationBuilder remoteBuilder = new ConfigurationBuilder(); ConfigurationBuilder remoteBuilder = new ConfigurationBuilder();
remoteBuilder.addServer() remoteBuilder.addServer()
.host(config.get("host", "localhost")) .host(config.get("host", "localhost"))
.port(config.getInt("port", 11444)) .port(config.getInt("port", 11222))
.clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE) .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
.marshaller(new ProtoStreamMarshaller()); .marshaller(new ProtoStreamMarshaller());

View file

@ -178,6 +178,7 @@
https://issues.redhat.com/browse/KEYCLOAK-17585?focusedCommentId=16002705&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-16002705 https://issues.redhat.com/browse/KEYCLOAK-17585?focusedCommentId=16002705&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-16002705
--> -->
<microprofile-metrics-api.version>2.3</microprofile-metrics-api.version> <microprofile-metrics-api.version>2.3</microprofile-metrics-api.version>
<testcontainers.version>1.16.3</testcontainers.version>
<!-- Maven Plugins --> <!-- Maven Plugins -->
<replacer.plugin.version>1.3.5</replacer.plugin.version> <replacer.plugin.version>1.3.5</replacer.plugin.version>

View file

@ -469,28 +469,6 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </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

@ -55,7 +55,6 @@
<arquillian-tomcat9-container-version>1.1.0.Final</arquillian-tomcat9-container-version> <arquillian-tomcat9-container-version>1.1.0.Final</arquillian-tomcat9-container-version>
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version> <undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
<version.org.wildfly.extras.creaper>1.6.1</version.org.wildfly.extras.creaper> <version.org.wildfly.extras.creaper>1.6.1</version.org.wildfly.extras.creaper>
<testcontainers.version>1.5.1</testcontainers.version>
<appium.client.version>6.1.0</appium.client.version> <appium.client.version>6.1.0</appium.client.version>
<!--migration properties--> <!--migration properties-->

View file

@ -48,3 +48,59 @@ mvn test -Pjpa -Dtest=ClientModelTest \
``` ```
The results are available in the `target/profile.html` file. The results are available in the `target/profile.html` file.
Usage of Testcontainers
-----------------------
Some profiles within model tests require running 3rd party software, for
example, database or Infinispan. For running these we are using
[Testcontainers](https://www.testcontainers.org/). This may require some
additional configuration of your container engine.
#### Podman settings
For more details see the following [Podman guide from Quarkus webpage](https://quarkus.io/guides/podman).
Specifically, these steps are required:
```shell
# Enable the podman socket with Docker REST API (only needs to be done once)
systemctl --user enable podman.socket --now
# Set the required environment variables (need to be run everytime or added to profile)
export DOCKER_HOST=unix:///run/user/${UID}/podman/podman.sock
```
Testcontainers are using [ryuk](https://hub.docker.com/r/testcontainers/ryuk)
to cleanup containers after tests. To make this work with Podman add the
following line to `~/.testcontainers.properties`
```shell
ryuk.container.privileged=true
```
Alternatively, disable usage of ryuk (using this may result in stale containers
still running after tests finish. This is not recommended especially if you are
executing tests from Intellij IDE as it [may not stop](https://youtrack.jetbrains.com/issue/IDEA-190385)
the containers created during test run).
```shell
export TESTCONTAINERS_RYUK_DISABLED=true #not recommended - see above!
```
#### Docker settings
To use Testcontainers with Docker it is necessary to
[make Docker available for non-root users](https://docs.docker.com/engine/install/linux-postinstall/).
Running HotRod tests with external Infinispan
---------------------------------------------
By default, Model tests with `hot-rod` profile spawn a new Infinispan container
with each test execution. It is also possible, to configure Model tests to
connect to an external instance of Infinispan. To do so, execute tests with
the following command:
```shell
mvn test -Phot-rod \
-Dhot-rod.start-container=false \
-Dhot-rod.connection.host=<host> \
-Dhot-rod.connection.port=<port> \
-Dhot-rod.connection.username=<username> \
-Dhot-rod.connection.password=<password>
```

View file

@ -120,6 +120,12 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>${postgresql.driver.version}</version> <version>${postgresql.driver.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -171,7 +177,14 @@
<keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase}</keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase> <keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase}</keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<org.jboss.logging.provider>log4j</org.jboss.logging.provider> <org.jboss.logging.provider>log4j</org.jboss.logging.provider>
<infinispan.version>${infinispan.version}</infinispan.version>
</systemPropertyVariables> </systemPropertyVariables>
<properties>
<property>
<name>listener</name>
<value>org.keycloak.testsuite.model.AfterSuiteListener</value>
</property>
</properties>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>

View file

@ -84,29 +84,6 @@ public class HotRodServerRule extends ExternalResource {
InfinispanUtil.setTimeServiceToKeycloakTime(hotRodCacheManager2); InfinispanUtil.setTimeServiceToKeycloakTime(hotRodCacheManager2);
} }
public void createHotRodMapStoreServer() {
hotRodCacheManager = configureHotRodCacheManager("hotrod/infinispan.xml");
hotRodServer = new HotRodServer();
HotRodUtils.createHotRodMapStoreServer(hotRodServer, hotRodCacheManager, 11444);
org.infinispan.client.hotrod.configuration.ConfigurationBuilder remoteBuilder = new org.infinispan.client.hotrod.configuration.ConfigurationBuilder();
org.infinispan.client.hotrod.configuration.Configuration cfg = remoteBuilder
.addServers(hotRodServer.getHost() + ":" + hotRodServer.getPort()).build();
remoteCacheManager = new RemoteCacheManager(cfg);
}
private DefaultCacheManager configureHotRodCacheManager(String configPath) {
DefaultCacheManager manager = null;
try {
manager = new DefaultCacheManager(configPath);
} catch (IOException e) {
new RuntimeException(e);
}
return manager;
}
private void getCaches(String... cache) { private void getCaches(String... cache) {
for (String c: cache) { for (String c: cache) {
hotRodCacheManager.getCache(c, true); hotRodCacheManager.getCache(c, true);

View file

@ -72,4 +72,8 @@ public class KeycloakModelParameters {
public void beforeSuite(Config cf) { public void beforeSuite(Config cf) {
} }
public void afterSuite() {
}
} }

View file

@ -0,0 +1,32 @@
/*
* 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.testsuite.model;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;
public class AfterSuiteListener extends RunListener {
@Override
public void testRunFinished(Result result) throws Exception {
KeycloakModelTest.closeKeycloakSessionFactory();
for (KeycloakModelParameters kmp : KeycloakModelTest.MODEL_PARAMETERS) {
kmp.afterSuite();
}
}
}

View file

@ -17,8 +17,7 @@
package org.keycloak.testsuite.model.parameters; package org.keycloak.testsuite.model.parameters;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.junit.runner.Description; import org.jboss.logging.Logger;
import org.junit.runners.model.Statement;
import org.keycloak.authorization.store.StoreFactorySpi; import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi; import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.ActionTokenStoreSpi; import org.keycloak.models.ActionTokenStoreSpi;
@ -31,7 +30,6 @@ import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderF
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
import org.keycloak.models.map.client.MapClientProviderFactory; import org.keycloak.models.map.client.MapClientProviderFactory;
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
import org.keycloak.models.map.events.MapEventStoreProviderFactory;
import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory; import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory;
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
@ -51,10 +49,14 @@ import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
import org.keycloak.sessions.AuthenticationSessionSpi; import org.keycloak.sessions.AuthenticationSessionSpi;
import org.keycloak.testsuite.model.Config; import org.keycloak.testsuite.model.Config;
import org.keycloak.testsuite.model.HotRodServerRule;
import org.keycloak.testsuite.model.KeycloakModelParameters; import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.time.Duration;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* *
@ -62,6 +64,24 @@ import java.util.Set;
*/ */
public class HotRodMapStorage extends KeycloakModelParameters { public class HotRodMapStorage extends KeycloakModelParameters {
private final Logger LOG = Logger.getLogger(getClass());
public static final String PORT = System.getProperty("hot-rod.connection.port", "11222");
public static String HOST = System.getProperty("hot-rod.connection.host");
public static final String USERNAME = System.getProperty("hot-rod.connection.username", "admin");
public static final String PASSWORD = System.getProperty("hot-rod.connection.password", "admin");
public static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("hot-rod.start-container", "true"));
private static final String ZERO_TO_255
= "(\\d{1,2}|(0|1)\\"
+ "d{2}|2[0-4]\\d|25[0-5])";
private static final String IP_ADDRESS_REGEX
= ZERO_TO_255 + "\\."
+ ZERO_TO_255 + "\\."
+ ZERO_TO_255 + "\\."
+ ZERO_TO_255;
private static final Pattern IP_ADDRESS_PATTERN = Pattern.compile("listening on (" + IP_ADDRESS_REGEX + "):" + PORT);
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder() static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(HotRodConnectionSpi.class) .add(HotRodConnectionSpi.class)
.build(); .build();
@ -69,10 +89,12 @@ public class HotRodMapStorage extends KeycloakModelParameters {
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder() static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
.add(HotRodMapStorageProviderFactory.class) .add(HotRodMapStorageProviderFactory.class)
.add(HotRodConnectionProviderFactory.class) .add(HotRodConnectionProviderFactory.class)
.add(ConcurrentHashMapStorageProviderFactory.class) // TODO: this should be removed when we have a HotRod implementation for each area
.build(); .build();
private HotRodServerRule hotRodServerRule = new HotRodServerRule(); private final GenericContainer<?> hotRodContainer = new GenericContainer("quay.io/infinispan/server:" + System.getProperty("infinispan.version"))
.withEnv("USER", USERNAME)
.withEnv("PASS", PASSWORD)
.withNetworkMode("host");
@Override @Override
public void updateConfig(Config cf) { public void updateConfig(Config cf) {
@ -99,19 +121,38 @@ public class HotRodMapStorage extends KeycloakModelParameters {
.config("dir", "${project.build.directory:target}") .config("dir", "${project.build.directory:target}")
.config("keyType.single-use-objects", "string"); .config("keyType.single-use-objects", "string");
if (HOST == null && START_CONTAINER) {
Matcher matcher = IP_ADDRESS_PATTERN.matcher(hotRodContainer.getLogs());
if (!matcher.find()) {
LOG.errorf("Cannot find IP address of the infinispan server in log:\\n%s ", hotRodContainer.getLogs());
throw new IllegalStateException("Cannot find IP address of the Infinispan server. See test log for Infinispan container log.");
}
HOST = matcher.group(1);
}
cf.spi(HotRodConnectionSpi.NAME).provider(DefaultHotRodConnectionProviderFactory.PROVIDER_ID) cf.spi(HotRodConnectionSpi.NAME).provider(DefaultHotRodConnectionProviderFactory.PROVIDER_ID)
.config("enableSecurity", "false") .config("host", HOST)
.config("configureRemoteCaches", "false"); .config("port", PORT)
.config("username", USERNAME)
.config("password", PASSWORD)
.config("configureRemoteCaches", "true");
} }
@Override @Override
public void beforeSuite(Config cf) { public void beforeSuite(Config cf) {
hotRodServerRule.createHotRodMapStoreServer(); if (START_CONTAINER) {
hotRodContainer
.withStartupTimeout(Duration.ofMinutes(5))
.waitingFor(Wait.forLogMessage(".*Infinispan Server.*started in.*", 1))
.start();
}
} }
@Override @Override
public Statement classRule(Statement base, Description description) { public void afterSuite() {
return hotRodServerRule.apply(base, description); if (START_CONTAINER) {
hotRodContainer.stop();
}
} }
public HotRodMapStorage() { public HotRodMapStorage() {

View file

@ -98,7 +98,6 @@ public class UserSessionConcurrencyTest extends KeycloakModelTest {
// This is basically the same as JpaMapKeycloakTransaction#read method is doing after calling lockUserSessionsForModification() method // This is basically the same as JpaMapKeycloakTransaction#read method is doing after calling lockUserSessionsForModification() method
if (isHotRodStore) { if (isHotRodStore) {
SYNC_USESSION.lock(); SYNC_USESSION.lock();
releaseLockOnTransactionCommit(session, SYNC_USESSION);
} }
UserSessionModel uSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, uId)); UserSessionModel uSession = lockUserSessionsForModification(session, () -> session.sessions().getUserSession(realm, uId));
@ -110,6 +109,10 @@ public class UserSessionConcurrencyTest extends KeycloakModelTest {
cSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state-" + n); cSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state-" + n);
if (isHotRodStore) {
releaseLockOnTransactionCommit(session, SYNC_USESSION);
}
return null; return null;
})); }));