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["sanity-check-zip"]="-Dtest=StartCommandDistTest,StartDevCommandDistTest,BuildAndStartDistTest,ImportAtStartupDistTest"
|
||||||
PARAMS["zip"]=""
|
PARAMS["zip"]=""
|
||||||
PARAMS["container"]="-Dkc.quarkus.tests.dist=docker"
|
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 install -pl quarkus/tests/integration -am -DskipTests
|
||||||
./mvnw test -pl quarkus/tests/integration ${PARAMS["${{ matrix.server }}"]} | misc/log/trimmer.sh
|
./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
|
| {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.
|
| 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.
|
The loadbalancer will detect the situation as `/lb-check` returns an error, and will fail over to the other site.
|
||||||
Future versions will detect this situation and do an automatic failover.
|
|
||||||
|
|
||||||
When the {jdgserver_name} cluster is restored, its data will be out-of-sync with {project_name}.
|
The setup is degraded until the {jdgserver_name} cluster is restored and the session data is re-synchronized to the primary.
|
||||||
Manual operations are required to get {jdgserver_name} in the primary site in sync with the secondary site.
|
| No data loss^3^
|
||||||
| Loss of service
|
| Seconds to minutes (depending on load balancer setup)
|
||||||
| Human intervention required
|
|
||||||
|
|
||||||
| Connectivity {jdgserver_name}
|
| Connectivity {jdgserver_name}
|
||||||
| If the connectivity between the two sites is lost, session information cannot be sent to the other site.
|
| 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);
|
.statusCode(404);
|
||||||
when().get("/q/health/ready").then()
|
when().get("/q/health/ready").then()
|
||||||
.statusCode(404);
|
.statusCode(404);
|
||||||
|
when().get("/lb-check").then()
|
||||||
|
.statusCode(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -61,6 +63,8 @@ public class HealthDistTest {
|
||||||
// Metrics should not be enabled
|
// Metrics should not be enabled
|
||||||
when().get("/metrics").then()
|
when().get("/metrics").then()
|
||||||
.statusCode(404);
|
.statusCode(404);
|
||||||
|
when().get("/lb-check").then()
|
||||||
|
.statusCode(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -72,6 +76,8 @@ public class HealthDistTest {
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.body("checks[0].name", equalTo("Keycloak database connections async health check"))
|
.body("checks[0].name", equalTo("Keycloak database connections async health check"))
|
||||||
.body("checks.size()", equalTo(1));
|
.body("checks.size()", equalTo(1));
|
||||||
|
when().get("/lb-check").then()
|
||||||
|
.statusCode(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,6 +89,8 @@ public class HealthDistTest {
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.body("checks[0].name", equalTo("Keycloak database connections health check"))
|
.body("checks[0].name", equalTo("Keycloak database connections health check"))
|
||||||
.body("checks.size()", equalTo(1));
|
.body("checks.size()", equalTo(1));
|
||||||
|
when().get("/lb-check").then()
|
||||||
|
.statusCode(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -125,4 +133,11 @@ public class HealthDistTest {
|
||||||
distribution.stop();
|
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.KeycloakPropertiesConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.configuration.test.TestConfigArgsConfigSource;
|
import org.keycloak.quarkus.runtime.configuration.test.TestConfigArgsConfigSource;
|
||||||
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
|
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -59,6 +61,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
||||||
private KeycloakDistribution dist;
|
private KeycloakDistribution dist;
|
||||||
private final Set<String> testSysProps = new HashSet<>();
|
private final Set<String> testSysProps = new HashSet<>();
|
||||||
private DatabaseContainer databaseContainer;
|
private DatabaseContainer databaseContainer;
|
||||||
|
private InfinispanContainer infinispanContainer;
|
||||||
private CLIResult result;
|
private CLIResult result;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -88,6 +91,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
configureDatabase(context);
|
configureDatabase(context);
|
||||||
|
infinispanContainer = configureExternalInfinispan(context);
|
||||||
|
|
||||||
if (distConfig != null) {
|
if (distConfig != null) {
|
||||||
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class));
|
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class));
|
||||||
|
@ -192,6 +196,9 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
||||||
databaseContainer.stop();
|
databaseContainer.stop();
|
||||||
databaseContainer = null;
|
databaseContainer = null;
|
||||||
}
|
}
|
||||||
|
if (infinispanContainer != null && infinispanContainer.isRunning()) {
|
||||||
|
infinispanContainer.stop();
|
||||||
|
}
|
||||||
result = null;
|
result = null;
|
||||||
if (RAW.equals(DistributionType.getCurrent().orElse(RAW))) {
|
if (RAW.equals(DistributionType.getCurrent().orElse(RAW))) {
|
||||||
if (distConfig != null && !DistributionTest.ReInstall.NEVER.equals(distConfig.reInstall()) && dist != null) {
|
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) {
|
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() {
|
private void configureDevServices() {
|
||||||
|
|
|
@ -50,17 +50,10 @@ public class DatabaseContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
void configureDistribution(KeycloakDistribution dist) {
|
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-username", getUsername());
|
||||||
dist.setProperty("db-password", getPassword());
|
dist.setProperty("db-password", getPassword());
|
||||||
dist.setProperty("db-url", getJdbcUrl());
|
dist.setProperty("db-url", getJdbcUrl());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private String getJdbcUrl() {
|
private String getJdbcUrl() {
|
||||||
return ((JdbcDatabaseContainer)container).getJdbcUrl();
|
return ((JdbcDatabaseContainer)container).getJdbcUrl();
|
||||||
|
@ -97,24 +90,10 @@ public class DatabaseContainer {
|
||||||
.withInitScript(resolveInitScript());
|
.withInitScript(resolveInitScript());
|
||||||
}
|
}
|
||||||
|
|
||||||
private GenericContainer<?> configureInfinispanUser(GenericContainer<?> infinispanContainer) {
|
|
||||||
infinispanContainer.addEnv("USER", getUsername());
|
|
||||||
infinispanContainer.addEnv("PASS", getPassword());
|
|
||||||
return infinispanContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GenericContainer<?> createContainer() {
|
private GenericContainer<?> createContainer() {
|
||||||
String POSTGRES_IMAGE = System.getProperty("kc.db.postgresql.container.image");
|
String POSTGRES_IMAGE = System.getProperty("kc.db.postgresql.container.image");
|
||||||
String MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image");
|
String MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image");
|
||||||
String MYSQL_IMAGE = System.getProperty("kc.db.mysql.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");
|
String MSSQL_IMAGE = System.getProperty("kc.db.mssql.container.image");
|
||||||
|
|
||||||
switch (alias) {
|
switch (alias) {
|
||||||
|
@ -130,14 +109,6 @@ public class DatabaseContainer {
|
||||||
case "mssql":
|
case "mssql":
|
||||||
DockerImageName MSSQL = DockerImageName.parse(MSSQL_IMAGE).asCompatibleSubstituteFor("sqlserver");
|
DockerImageName MSSQL = DockerImageName.parse(MSSQL_IMAGE).asCompatibleSubstituteFor("sqlserver");
|
||||||
return configureJdbcContainer(new MSSQLServerContainer<>(MSSQL));
|
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:
|
default:
|
||||||
throw new RuntimeException("Unsupported database: " + alias);
|
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