Add tests for lb-check endpoint
Added documentation why the check retries and updated outdated docs Closes #25113 Signed-off-by: Michal Hajas <mhajas@redhat.com> Signed-off-by: Alexander Schwartz <aschwart@redhat.com> Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
10bcd896a9
commit
d387f13525
9 changed files with 499 additions and 40 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -185,7 +185,7 @@ jobs:
|
|||
PARAMS["sanity-check-zip"]="-Dtest=StartCommandDistTest,StartDevCommandDistTest,BuildAndStartDistTest,ImportAtStartupDistTest"
|
||||
PARAMS["zip"]=""
|
||||
PARAMS["container"]="-Dkc.quarkus.tests.dist=docker"
|
||||
PARAMS["storage"]="-Ptest-database -Dtest=PostgreSQLDistTest,MariaDBDistTest#testSuccessful,MySQLDistTest#testSuccessful,DatabaseOptionsDistTest,JPAStoreDistTest,HotRodStoreDistTest,MixedStoreDistTest,TransactionConfigurationDistTest"
|
||||
PARAMS["storage"]="-Ptest-database -Dtest=PostgreSQLDistTest,MariaDBDistTest#testSuccessful,MySQLDistTest#testSuccessful,DatabaseOptionsDistTest,JPAStoreDistTest,HotRodStoreDistTest,MixedStoreDistTest,TransactionConfigurationDistTest,ExternalInfinispanTest"
|
||||
|
||||
./mvnw install -pl quarkus/tests/integration -am -DskipTests
|
||||
./mvnw test -pl quarkus/tests/integration ${PARAMS["${{ matrix.server }}"]} | misc/log/trimmer.sh
|
||||
|
|
|
@ -62,13 +62,11 @@ Monitoring is necessary to detect degraded setups.
|
|||
|
||||
| {jdgserver_name} cluster failure
|
||||
| If the {jdgserver_name} cluster fails in the active site, {project_name} will not be able to communicate with the external {jdgserver_name}, and the {project_name} service will be unavailable.
|
||||
Manual switchover to the secondary site is recommended.
|
||||
Future versions will detect this situation and do an automatic failover.
|
||||
The loadbalancer will detect the situation as `/lb-check` returns an error, and will fail over to the other site.
|
||||
|
||||
When the {jdgserver_name} cluster is restored, its data will be out-of-sync with {project_name}.
|
||||
Manual operations are required to get {jdgserver_name} in the primary site in sync with the secondary site.
|
||||
| Loss of service
|
||||
| Human intervention required
|
||||
The setup is degraded until the {jdgserver_name} cluster is restored and the session data is re-synchronized to the primary.
|
||||
| No data loss^3^
|
||||
| Seconds to minutes (depending on load balancer setup)
|
||||
|
||||
| Connectivity {jdgserver_name}
|
||||
| If the connectivity between the two sites is lost, session information cannot be sent to the other site.
|
||||
|
|
|
@ -47,6 +47,8 @@ public class HealthDistTest {
|
|||
.statusCode(404);
|
||||
when().get("/q/health/ready").then()
|
||||
.statusCode(404);
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -61,6 +63,8 @@ public class HealthDistTest {
|
|||
// Metrics should not be enabled
|
||||
when().get("/metrics").then()
|
||||
.statusCode(404);
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -72,6 +76,8 @@ public class HealthDistTest {
|
|||
.statusCode(200)
|
||||
.body("checks[0].name", equalTo("Keycloak database connections async health check"))
|
||||
.body("checks.size()", equalTo(1));
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -83,6 +89,8 @@ public class HealthDistTest {
|
|||
.statusCode(200)
|
||||
.body("checks[0].name", equalTo("Keycloak database connections health check"))
|
||||
.body("checks.size()", equalTo(1));
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -125,4 +133,11 @@ public class HealthDistTest {
|
|||
distribution.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "start-dev", "--features=multi-site" })
|
||||
void testLoadBalancerCheck() {
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2021 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.database;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
import org.keycloak.it.junit5.extension.InfinispanContainer;
|
||||
import org.keycloak.it.junit5.extension.WithExternalInfinispan;
|
||||
|
||||
import static io.restassured.RestAssured.when;
|
||||
|
||||
@DistributionTest(keepAlive = true)
|
||||
@WithExternalInfinispan
|
||||
public class ExternalInfinispanTest {
|
||||
|
||||
@Test
|
||||
@Launch({ "start-dev", "--features=multi-site", "--cache=ispn", "--cache-config-file=../../../test-classes/ExternalInfinispan/kcb-infinispan-cache-remote-store-config.xml", "-Djboss.site.name=ISPN" })
|
||||
void testLoadBalancerCheckFailure() {
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(200);
|
||||
|
||||
InfinispanContainer.remoteCacheManager.administration().removeCache("sessions");
|
||||
|
||||
// The `lb-check` relies on the Infinispan's persistence check status. By default, Infinispan checks in the background every second that the remote store is available.
|
||||
// So we'll wait on average about one second here for the check to switch its state.
|
||||
Retry.execute(() -> {
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(503);
|
||||
}, 10, 200);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- end::keycloak-ispn-configmap[] -->
|
||||
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<!--tag::keycloak-ispn-configmap[] -->
|
||||
<infinispan
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="urn:infinispan:config:14.0 https://www.infinispan.org/schemas/infinispan-config-14.0.xsd
|
||||
urn:infinispan:config:store:remote:14.0 https://www.infinispan.org/schemas/infinispan-cachestore-remote-config-14.0.xsd"
|
||||
xmlns="urn:infinispan:config:14.0">
|
||||
<!--end::keycloak-ispn-configmap[] -->
|
||||
|
||||
<!-- the statistics="true" attribute is not part of the original KC config and was added by Keycloak Benchmark -->
|
||||
<cache-container name="keycloak" statistics="true">
|
||||
<transport lock-timeout="60000"/>
|
||||
<metrics names-as-tags="true" />
|
||||
<local-cache name="realms" simple-cache="true" statistics="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users" simple-cache="true" statistics="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<!--tag::keycloak-ispn-remotestore[] -->
|
||||
<distributed-cache name="sessions" owners="2" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false"> <!--1-->
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="sessions"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/> <!--2-->
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/> <!--3-->
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/> <!--5-->
|
||||
</distributed-cache>
|
||||
<!--end::keycloak-ispn-remotestore[] -->
|
||||
<distributed-cache name="authenticationSessions" owners="2" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="authenticationSessions"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineSessions" owners="2" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="offlineSessions"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="clientSessions" owners="2" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="clientSessions"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineClientSessions" owners="2" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="offlineClientSessions"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="loginFailures" owners="2" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="loginFailures"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/>
|
||||
</distributed-cache>
|
||||
<local-cache name="authorization" simple-cache="true" statistics="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<replicated-cache name="work" statistics="true">
|
||||
<expiration lifespan="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="work"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
|
||||
</replicated-cache>
|
||||
<local-cache name="keys" simple-cache="true" statistics="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="3600000"/>
|
||||
<memory max-count="1000"/>
|
||||
</local-cache>
|
||||
<distributed-cache name="actionTokens" owners="2" statistics="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="-1" lifespan="-1" interval="300000"/>
|
||||
<memory max-count="-1"/>
|
||||
<persistence passivation="false">
|
||||
<remote-store xmlns="urn:infinispan:config:store:remote:14.0"
|
||||
cache="actionTokens"
|
||||
raw-values="true"
|
||||
shared="true"
|
||||
segmented="false">
|
||||
<remote-server host="localhost"
|
||||
port="11222"/>
|
||||
<connection-pool max-active="16"
|
||||
exhausted-action="CREATE_NEW"/>
|
||||
<security>
|
||||
<authentication server-name="infinispan">
|
||||
<digest username="keycloak"
|
||||
password="Password1!"
|
||||
realm="default"/>
|
||||
</authentication>
|
||||
|
||||
</security>
|
||||
</remote-store>
|
||||
</persistence>
|
||||
<state-transfer enabled="false"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
|
@ -35,8 +35,10 @@ import org.keycloak.quarkus.runtime.cli.command.StartDev;
|
|||
import org.keycloak.quarkus.runtime.configuration.KeycloakPropertiesConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.test.TestConfigArgsConfigSource;
|
||||
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -59,6 +61,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
|||
private KeycloakDistribution dist;
|
||||
private final Set<String> testSysProps = new HashSet<>();
|
||||
private DatabaseContainer databaseContainer;
|
||||
private InfinispanContainer infinispanContainer;
|
||||
private CLIResult result;
|
||||
|
||||
@Override
|
||||
|
@ -88,6 +91,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
|||
}
|
||||
|
||||
configureDatabase(context);
|
||||
infinispanContainer = configureExternalInfinispan(context);
|
||||
|
||||
if (distConfig != null) {
|
||||
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class));
|
||||
|
@ -192,6 +196,9 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
|||
databaseContainer.stop();
|
||||
databaseContainer = null;
|
||||
}
|
||||
if (infinispanContainer != null && infinispanContainer.isRunning()) {
|
||||
infinispanContainer.stop();
|
||||
}
|
||||
result = null;
|
||||
if (RAW.equals(DistributionType.getCurrent().orElse(RAW))) {
|
||||
if (distConfig != null && !DistributionTest.ReInstall.NEVER.equals(distConfig.reInstall()) && dist != null) {
|
||||
|
@ -324,8 +331,24 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
|||
}
|
||||
}
|
||||
|
||||
private static InfinispanContainer configureExternalInfinispan(ExtensionContext context) {
|
||||
if (getAnnotationFromTestContext(context, WithExternalInfinispan.class) != null) {
|
||||
InfinispanContainer infinispanContainer = new InfinispanContainer();
|
||||
infinispanContainer.start();
|
||||
return infinispanContainer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static WithDatabase getDatabaseConfig(ExtensionContext context) {
|
||||
return context.getTestClass().orElse(Object.class).getDeclaredAnnotation(WithDatabase.class);
|
||||
return getAnnotationFromTestContext(context, WithDatabase.class);
|
||||
}
|
||||
|
||||
private static <T extends Annotation> T getAnnotationFromTestContext(ExtensionContext context, Class<T> annotationClass) {
|
||||
return context.getTestClass().map(c -> c.getDeclaredAnnotation(annotationClass))
|
||||
.or(() -> context.getTestMethod().map(m -> m.getAnnotation(annotationClass)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private void configureDevServices() {
|
||||
|
|
|
@ -50,17 +50,10 @@ public class DatabaseContainer {
|
|||
}
|
||||
|
||||
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.getHost());
|
||||
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();
|
||||
|
@ -97,24 +90,10 @@ public class DatabaseContainer {
|
|||
.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");
|
||||
String MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image");
|
||||
String MYSQL_IMAGE = System.getProperty("kc.db.mysql.container.image");
|
||||
String INFINISPAN_IMAGE = System.getProperty("kc.infinispan.container.image");
|
||||
if (INFINISPAN_IMAGE.matches("quay.io/infinispan/.*-SNAPSHOT")) {
|
||||
// If the image name ends with SNAPSHOT, someone is trying to use a snapshot release of Infinispan.
|
||||
// Then switch to the closest match of the Infinispan test container
|
||||
INFINISPAN_IMAGE = INFINISPAN_IMAGE.replaceAll("quay.io/infinispan/", "quay.io/infinispan-test/");
|
||||
INFINISPAN_IMAGE = INFINISPAN_IMAGE.replaceAll("[0-9]*-SNAPSHOT$", "x");
|
||||
}
|
||||
|
||||
String MSSQL_IMAGE = System.getProperty("kc.db.mssql.container.image");
|
||||
|
||||
switch (alias) {
|
||||
|
@ -130,14 +109,6 @@ public class DatabaseContainer {
|
|||
case "mssql":
|
||||
DockerImageName MSSQL = DockerImageName.parse(MSSQL_IMAGE).asCompatibleSubstituteFor("sqlserver");
|
||||
return configureJdbcContainer(new MSSQLServerContainer<>(MSSQL));
|
||||
case "infinispan":
|
||||
GenericContainer<?> infinispanContainer = configureInfinispanUser(new GenericContainer<>(INFINISPAN_IMAGE))
|
||||
.withExposedPorts(11222);
|
||||
// the images in the 'infinispan-test' repository point to tags that are frequently refreshed, therefore, always pull them
|
||||
if (infinispanContainer.getDockerImageName().startsWith("quay.io/infinispan-test")) {
|
||||
infinispanContainer.withImagePullPolicy(PullPolicy.alwaysPull());
|
||||
}
|
||||
return infinispanContainer;
|
||||
default:
|
||||
throw new RuntimeException("Unsupported database: " + alias);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright 2023 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.junit5.extension;
|
||||
|
||||
import org.infinispan.client.hotrod.RemoteCacheManager;
|
||||
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
|
||||
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
|
||||
import org.infinispan.commons.configuration.XMLStringConfiguration;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.images.PullPolicy;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
|
||||
|
||||
private final Logger LOG = Logger.getLogger(getClass());
|
||||
public static final String PORT = System.getProperty("keycloak.externalInfinispan.port", "11222");
|
||||
public static final String USERNAME = System.getProperty("keycloak.externalInfinispan.username", "keycloak");
|
||||
public static final String PASSWORD = System.getProperty("keycloak.externalInfinispan.password", DatabaseContainer.DEFAULT_PASSWORD);
|
||||
|
||||
public static RemoteCacheManager remoteCacheManager;
|
||||
|
||||
public InfinispanContainer() {
|
||||
super(getImageName());
|
||||
withEnv("USER", USERNAME);
|
||||
withEnv("PASS", PASSWORD);
|
||||
withNetworkMode("host");
|
||||
|
||||
// the images in the 'infinispan-test' repository point to tags that are frequently refreshed, therefore, always pull them
|
||||
if (getImageName().startsWith("quay.io/infinispan-test")) {
|
||||
withImagePullPolicy(PullPolicy.alwaysPull());
|
||||
}
|
||||
|
||||
|
||||
//order of waitingFor and withStartupTimeout matters as the latter sets the timeout for WaitStrategy set by waitingFor
|
||||
waitingFor(Wait.forLogMessage(".*Infinispan Server.*started in.*", 1));
|
||||
withStartupTimeout(Duration.ofMinutes(5));
|
||||
}
|
||||
|
||||
private static String getImageName() {
|
||||
String INFINISPAN_IMAGE = System.getProperty("kc.infinispan.container.image");
|
||||
if (INFINISPAN_IMAGE.matches("quay.io/infinispan/.*-SNAPSHOT")) {
|
||||
// If the image name ends with SNAPSHOT, someone is trying to use a snapshot release of Infinispan.
|
||||
// Then switch to the closest match of the Infinispan test container
|
||||
INFINISPAN_IMAGE = INFINISPAN_IMAGE.replaceAll("quay.io/infinispan/", "quay.io/infinispan-test/");
|
||||
INFINISPAN_IMAGE = INFINISPAN_IMAGE.replaceAll("[0-9]*-SNAPSHOT$", "x");
|
||||
}
|
||||
|
||||
return INFINISPAN_IMAGE;
|
||||
}
|
||||
|
||||
private void establishHotRodConnection() {
|
||||
ConfigurationBuilder configBuilder = new ConfigurationBuilder()
|
||||
.addServers(getContainerIpAddress() + ":11222")
|
||||
.security()
|
||||
.authentication()
|
||||
.username(getUsername())
|
||||
.password(getPassword())
|
||||
.clientIntelligence(ClientIntelligence.BASIC);
|
||||
|
||||
configBuilder.statistics().enable()
|
||||
.statistics().jmxEnable();
|
||||
|
||||
remoteCacheManager = new RemoteCacheManager(configBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
establishHotRodConnection();
|
||||
|
||||
Stream.of("sessions", "actionTokens", "authenticationSessions", "clientSessions", "offlineSessions", "offlineClientSessions", "loginFailures", "work")
|
||||
.forEach(cacheName -> {
|
||||
LOG.infof("Creating cache '%s'", cacheName);
|
||||
createCache(remoteCacheManager, cacheName);
|
||||
});
|
||||
}
|
||||
|
||||
public void createCache(RemoteCacheManager remoteCacheManager, String cacheName) {
|
||||
String xml = String.format("<distributed-cache name=\"%s\" mode=\"SYNC\" owners=\"2\"></distributed-cache>" , cacheName);
|
||||
remoteCacheManager.administration().getOrCreateCache(cacheName, new XMLStringConfiguration(xml));
|
||||
}
|
||||
|
||||
public String getPort() {
|
||||
return PORT;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return USERNAME;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return PASSWORD;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2021 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.junit5.extension;
|
||||
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@link WithExternalInfinispan} is used to start an Infinispan container.
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@EnabledIfSystemProperty(named = "kc.test.storage.database", matches = "true", disabledReason = "Docker takes too much time and stability depends on the environment. We should try running these tests in CI but isolated.")
|
||||
public @interface WithExternalInfinispan {
|
||||
|
||||
}
|
Loading…
Reference in a new issue