Delete container providers from the base testsuite (#25168)

Closes #24097

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
Vlasta Ramik 2023-12-04 14:44:35 +01:00 committed by GitHub
parent 8b5ebb6597
commit 6f37fefd8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1 additions and 1027 deletions

View file

@ -957,136 +957,6 @@ when running these tests on your local machine. This happens when something on y
In order to avoid using external services for DNS resolution, the tests are executed using a local host file by setting the `-Djdk.net.hosts.file=${project.build.testOutputDirectory}/hosts_file`
system property.
## Running base testsuite with Map storage
To run base testsuite with new storage run the following command (this will execute testsuite with ConcurrentHashMap storage):
```shell
mvn clean install -f testsuite/integration-arquillian/tests/base \
-Pauth-server-quarkus -Pmap-storage-chm
```
### Running tests with JPA Map storage
By default, testing with the profile `map-storage-jpa-postgres` spawns a new Postgres container
with each test execution. The default image used is `postgres:alpine`. To spawn a different
version, use the system property `keycloak.map.storage.postgres.docker.image`.
In a similar way the profile `map-storage-jpa-cockroach` spawns a new CockroachDB container
with each test execution. It uses the official CockroachDB image in the version stated in the
class `CockroachdbContainerTestEnricher`. To spawn a different
version, use the system property `keycloak.map.storage.cockroachdb.docker.image`.
Execute tests:
```shell
mvn clean install -f testsuite/integration-arquillian/tests/base \
-Pmap-storage-jpa-postgres
```
It's also possible to configure tests to connect to an external database, it might be useful
for debugging purposes as the database is not removed after the testsuite run. On the other hand
it'll require manual cleaning between two runs.
PostgreSQL database can be started e.g. by following command:
```shell
podman run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=pass -e POSTGRES_USER=keycloak -e POSTGRES_DB=keycloak -d postgres:alpine
```
To run the tests without spawning the container for you, execute tests with the following command:
```shell
mvn clean install -f testsuite/integration-arquillian/tests/base \
-Pmap-storage-jpa-postgres \
-Dpostgres.start-container=false \
-Dkeycloak.map.storage.connectionsJpa.url=<jdbc_url> \
-Dkeycloak.map.storage.connectionsJpa.user=<user> \
-Dkeycloak.map.storage.connectionsJpa.password=<password>
```
### Running tests with HotRod Map storage
By default, Base testsuite with `map-storage-hotrod` profile spawn a new Infinispan container
with each test execution. To run the tests execute:
```shell
mvn clean install -f testsuite/integration-arquillian/tests/base \
-Pmap-storage-hotrod
```
Note: For running Infinispan server we are using Testcontainer, see section
_Usage of Testcontainers_ for details on how to set up your container engine.
It is also possible, to configure Base testsuite to
connect to an external instance of Infinispan. To do so, execute tests with
the following command:
```shell
mvn clean install -f testsuite/integration-arquillian/tests/base \
-Pmap-storage-hotrod
-Dkeycloak.testsuite.start-hotrod-container=false \
-Dkeycloak.connectionsHotRod.host=<host> \
-Dkeycloak.connectionsHotRod.port=<port> \
-Dkeycloak.connectionsHotRod.username=<username> \
-Dkeycloak.connectionsHotRod.password=<password>
```
### 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/).
### Zero downtime tests
By default tests are enabled and runs when `map-storage-hotrod`, `map-storage-postgres` or `map-storage-jpa-cockroach` profile is enabled.
Other may be added in future. It supports both `auth-server-undertow` and `auth-server-quarkus`.
#### Default behavior
1. Before test current `auth-server` is stopped as well as ( postgres/crdb/hotrod ) container.
2. New ( postgres/crdb/hotrod ) container is spawned using testcontainers.
3. Legacy keycloak (latest from https://quay.io/) is started.
4. Test realm is imported into legacy keycloak.
5. Current `auth-server` is started.
#### Notes
- version of legacy keycloak could be specified by `keycloak.legacy.version.zero.downtime` property
- legacy keycloak is by default listening on http://localhost:8091
- when running only zero downtime tests, e.g. locally, it could be speeded up by skipping start of suite
( postgres/crdb/hotrod ) container by `-Dcockroachdb.start-container=false`, `-Dpostgres.start-container=false`
or `-Dkeycloak.testsuite.start-hotrod-container=false` and also suite `auth-server` by `-Dkeycloak.testsuite.skip.start.auth.server=true`
- to run tests with an external instance of ( postgres/crdb/hotrod ) container
`-Dpostgres.start-container-for-zero-downtime=false`; `-Dcockroachdb.start-container-for-zero-downtime`; `-Dstart-hotrod-container-for-zero-downtime`
together with appropriate properties (see sections above).
## FIPS 140-2 testing
### Unit tests

View file

@ -112,16 +112,6 @@
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>cockroachdb</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View file

@ -1,81 +0,0 @@
/*
* 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.testsuite.arquillian;
import java.lang.annotation.Annotation;
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
import org.jboss.logging.Logger;
import org.testcontainers.containers.CockroachContainer;
import org.testcontainers.utility.DockerImageName;
public class CockroachdbContainerProvider implements ResourceProvider {
private static final Logger log = Logger.getLogger(CockroachdbContainerProvider.class);
private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("cockroachdb.start-container", "false"));
private static final String COCKROACHDB_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.cockroachdb.docker.image", "cockroachdb/cockroach:v22.1.0");
public static final String COCKROACHDB_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak");
public static final String COCKROACHDB_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass");
private static CockroachContainer cockroachDbContainer;
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
if (START_CONTAINER) {
cockroachDbContainer = createContainer();
cockroachDbContainer.start();
System.setProperty("keycloak.map.storage.connectionsJpa.url", cockroachDbContainer.getJdbcUrl());
log.infof("DatabaseInfo: %s, user=%s, pass=%s", cockroachDbContainer.getJdbcUrl(), COCKROACHDB_DB_USER, COCKROACHDB_DB_PASSWORD);
}
}
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
if (START_CONTAINER) {
cockroachDbContainer.stop();
}
}
public CockroachContainer createContainer() {
return new CockroachContainer(DockerImageName.parse(COCKROACHDB_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("cockroachdb"))
// Using the environment variables for now where using the withXXX() method is not supported, yet.
// https://github.com/testcontainers/testcontainers-java/issues/6299
.withEnv("COCKROACH_DATABASE", "keycloak")
.withEnv("COCKROACH_USER", COCKROACHDB_DB_USER)
// password is not used/supported in insecure mode
.withCommand("start-single-node", "--insecure");
}
public CockroachContainer getContainer() {
return cockroachDbContainer;
}
@Override
public boolean canProvide(Class<?> type) {
return type.equals(CockroachdbContainerProvider.class);
}
@Override
public Object lookup(ArquillianResource ar, Annotation... antns) {
return this;
}
}

View file

@ -1,63 +0,0 @@
/*
* 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.testsuite.arquillian;
import java.lang.annotation.Annotation;
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
import org.keycloak.testsuite.util.InfinispanContainer;
public class HotRodContainerProvider implements ResourceProvider {
public static final String HOT_ROD_STORE_HOST_PROPERTY = "keycloak.connectionsHotRod.host";
public static final boolean HOT_ROD_START_CONTAINER = Boolean.parseBoolean(System.getProperty("keycloak.testsuite.start-hotrod-container", "false"));
private static InfinispanContainer infinispanContainer;
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
if (!HOT_ROD_START_CONTAINER) return;
infinispanContainer = new InfinispanContainer();
infinispanContainer.start();
// Add env variable, so it can be picked up by Keycloak
System.setProperty(HOT_ROD_STORE_HOST_PROPERTY, infinispanContainer.getHost());
}
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
if (!HOT_ROD_START_CONTAINER) return;
if (infinispanContainer != null) infinispanContainer.stop();
}
public InfinispanContainer getContainer() {
return infinispanContainer;
}
@Override
public boolean canProvide(Class<?> type) {
return type.equals(HotRodContainerProvider.class);
}
@Override
public Object lookup(ArquillianResource ar, Annotation... antns) {
return this;
}
}

View file

@ -58,11 +58,7 @@ public class KeycloakArquillianExtension implements LoadableExtension {
.service(ResourceProvider.class, TestContextProvider.class)
.service(ResourceProvider.class, AdminClientProvider.class)
.service(ResourceProvider.class, OAuthClientProvider.class)
.service(ResourceProvider.class, LoadBalancerControllerProvider.class)
.service(ResourceProvider.class, PostgresContainerProvider.class)
.service(ResourceProvider.class, CockroachdbContainerProvider.class)
.service(ResourceProvider.class, HotRodContainerProvider.class)
.service(ResourceProvider.class, LegacyKeycloakContainerProvider.class);
.service(ResourceProvider.class, LoadBalancerControllerProvider.class);
builder
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
@ -72,9 +68,6 @@ public class KeycloakArquillianExtension implements LoadableExtension {
.observer(AuthServerTestEnricher.class)
.observer(AppServerTestEnricher.class)
.observer(CrossDCTestEnricher.class)
.observer(HotRodContainerProvider.class)
.observer(PostgresContainerProvider.class)
.observer(CockroachdbContainerProvider.class)
.observer(H2TestEnricher.class);
builder
.service(TestExecutionDecider.class, MigrationTestExecutionDecider.class)

View file

@ -1,111 +0,0 @@
/*
* 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.testsuite.arquillian;
import java.lang.annotation.Annotation;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.keycloak.testsuite.util.InfinispanContainer;
import org.keycloak.testsuite.util.LegacyKeycloakContainer;
public class LegacyKeycloakContainerProvider implements ResourceProvider {
public static final ZeroDowntimeContainer CONTAINER = processSystemProperties();
public enum ZeroDowntimeContainer {
POSTGRES,
COCKROACH,
HOTROD,
NONE;
}
private final String legacyKeycloakPort = System.getProperty("keycloak.legacy.port", "8091");
@Override
public boolean canProvide(Class<?> type) {
return type.equals(LegacyKeycloakContainerProvider.class);
}
@Override
public Object lookup(ArquillianResource ar, Annotation... antns) {
return this;
}
private static ZeroDowntimeContainer processSystemProperties() {
String mapStorageProfileconfig = System.getProperty("auth.server.quarkus.mapStorage.profile.config");
if (mapStorageProfileconfig == null) return ZeroDowntimeContainer.NONE;
switch (mapStorageProfileconfig) {
case "jpa":
String storageConnectionsVendor = System.getProperty("keycloak.storage.connections.vendor");
if (storageConnectionsVendor == null) return ZeroDowntimeContainer.NONE;
switch (storageConnectionsVendor) {
case "postgres":
return ZeroDowntimeContainer.POSTGRES;
case "cockroach":
return ZeroDowntimeContainer.COCKROACH;
default:
return ZeroDowntimeContainer.NONE;
}
case "hotrod":
return ZeroDowntimeContainer.HOTROD;
default:
return ZeroDowntimeContainer.NONE;
}
}
private LegacyKeycloakContainer createLegacyKeycloakContainer() {
return new LegacyKeycloakContainer(System.getProperty("keycloak.legacy.version.zero.downtime", "latest"));
}
public LegacyKeycloakContainer get() {
switch (CONTAINER) {
case POSTGRES:
return createLegacyKeycloakContainer().withCommand("start-dev",
"--storage=jpa",
"--http-port=" + legacyKeycloakPort,
"--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"),
"--db-username=" + PostgresContainerProvider.POSTGRES_DB_USER,
"--db-password=" + PostgresContainerProvider.POSTGRES_DB_PASSWORD);
case COCKROACH:
return createLegacyKeycloakContainer().withCommand("start-dev",
"--storage=jpa",
"--http-port=" + legacyKeycloakPort,
"--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"),
"--db-username=" + CockroachdbContainerProvider.COCKROACHDB_DB_USER,
"--db-password=" + CockroachdbContainerProvider.COCKROACHDB_DB_PASSWORD);
case HOTROD:
return createLegacyKeycloakContainer().withCommand("start-dev",
"--storage=hotrod",
"--http-port=" + legacyKeycloakPort,
"--storage-hotrod-host=" + System.getProperty(HotRodContainerProvider.HOT_ROD_STORE_HOST_PROPERTY),
"--storage-hotrod-port=" + InfinispanContainer.PORT,
"--storage-hotrod-username=" + InfinispanContainer.USERNAME,
"--storage-hotrod-password=" + InfinispanContainer.PASSWORD);
case NONE:
default:
return null;
}
}
}

View file

@ -1,80 +0,0 @@
/*
* 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.testsuite.arquillian;
import java.lang.annotation.Annotation;
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
import org.jboss.logging.Logger;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
public class PostgresContainerProvider implements ResourceProvider {
private final Logger log = Logger.getLogger(PostgresContainerProvider.class);
private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("postgres.start-container", "false"));
private static final String POSTGRES_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.postgres.docker.image", "postgres:alpine");
public static final String POSTGRES_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak");
public static final String POSTGRES_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass");
private static PostgreSQLContainer postgresContainer;
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
if (START_CONTAINER) {
postgresContainer = createContainer();
postgresContainer.start();
System.setProperty("keycloak.map.storage.connectionsJpa.url", postgresContainer.getJdbcUrl());
log.infof("DatabaseInfo: %s, user=%s, pass=%s", postgresContainer.getJdbcUrl(), POSTGRES_DB_USER, POSTGRES_DB_PASSWORD);
}
}
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
if (START_CONTAINER) {
postgresContainer.stop();
}
}
public PostgreSQLContainer createContainer() {
return new PostgreSQLContainer(DockerImageName.parse(POSTGRES_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("postgres"))
.withDatabaseName("keycloak")
.withUsername(POSTGRES_DB_USER)
.withPassword(POSTGRES_DB_PASSWORD);
}
public PostgreSQLContainer getContainer() {
return postgresContainer;
}
@Override
public boolean canProvide(Class<?> type) {
return type.equals(PostgresContainerProvider.class);
}
@Override
public Object lookup(ArquillianResource ar, Annotation... antns) {
return this;
}
}

View file

@ -1,121 +0,0 @@
/*
* 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.util;
import org.jboss.logging.Logger;
import org.keycloak.testsuite.arquillian.HotRodContainerProvider;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.MountableFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
private final Logger LOG = Logger.getLogger(getClass());
public static final String PORT = System.getProperty("keycloak.connectionsHotRod.port", "11222");
private static String HOST = System.getProperty(HotRodContainerProvider.HOT_ROD_STORE_HOST_PROPERTY);
public static final String USERNAME = System.getProperty("keycloak.connectionsHotRod.username", "admin");
public static final String PASSWORD = System.getProperty("keycloak.connectionsHotRod.password", "admin");
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);
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());
}
Path dir = Path.of(Path.of("").toAbsolutePath() + "/target/lib");
String projectVersion = System.getProperty("project.version");
Path timeTaskPath;
try {
timeTaskPath = Files.find(dir, 1, (path, attr) -> path.toString()
.endsWith("integration-arquillian-testsuite-providers-" + projectVersion + ".jar")).findFirst().orElse(null);
} catch (IOException e) {
throw new RuntimeException(e);
}
MountableFile mountableFile = MountableFile.forHostPath(timeTaskPath, 0666);
withCopyFileToContainer(mountableFile, "/opt/infinispan/server/lib/integration-arquillian-testsuite-providers.jar");
//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 version = System.getProperty("infinispan.version");
if (version.endsWith("-SNAPSHOT")) {
// for snapshot versions, '14.0.13-SNAPSHOT' translates to '14.0.x'
version = version.replaceAll("[0-9]*-SNAPSHOT$", "x");
return "quay.io/infinispan-test/server:" + version;
} else {
return "quay.io/infinispan/server:" + version;
}
}
@Override
public String getHost() {
if (HOST == null && this.isRunning()) {
Matcher matcher = IP_ADDRESS_PATTERN.matcher(getLogs());
if (!matcher.find()) {
LOG.errorf("Cannot find IP address of the infinispan server in log:\\n%s ", getLogs());
throw new IllegalStateException("Cannot find IP address of the Infinispan server. See test log for Infinispan container log.");
}
HOST = matcher.group(1);
if ("0.0.0.0".equals(HOST)) {
HOST = "127.0.0.1";
}
}
return HOST;
}
public String getPort() {
return PORT;
}
public String getUsername() {
return USERNAME;
}
public String getPassword() {
return PASSWORD;
}
}

View file

@ -1,86 +0,0 @@
/*
* 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.testsuite.util;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import org.keycloak.util.JsonSerialization;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
public class LegacyKeycloakContainer extends GenericContainer<LegacyKeycloakContainer> {
private static final String USERNAME = "admin";
private static final String PASSWORD = "admin";
public LegacyKeycloakContainer(String tagName) {
super("quay.io/keycloak/keycloak" + getManifestDigestOfImageByName(tagName));
withEnv("KEYCLOAK_ADMIN", USERNAME);
withEnv("KEYCLOAK_ADMIN_PASSWORD", PASSWORD);
withNetworkMode("host");
//order of waitingFor and withStartupTimeout matters as the latter sets the timeout for WaitStrategy set by waitingFor
waitingFor(Wait.forLogMessage(".*Running the server in development mode..*", 1));
withStartupTimeout(Duration.ofMinutes(5));
}
/**
* For tag names as "latest" or "nightly" it may happen that some older version keycloak could be cached.
* Therefore fetching container by its name wouldn't be reliable.
*
* This method obtains image "manifest_digest" by the name from quay.io and returns it with prefix "@".
* It can be then used to fetch the container with:
*
* <p>quay.io/keycloak/keycloak@sha256:...</p>
*
* @param tagName Name of the image tag.
* @return "manifest_digest" of the image if "latest" or "nightly" with prefix "@", {@code tagName} with ":" prefix otherwise
*/
private static String getManifestDigestOfImageByName(String tagName) {
if ("latest".equals(tagName) || "nightly".equals(tagName)) {
try {
URI uri = new URI("https://quay.io/api/v1/repository/keycloak/keycloak/tag/?specificTag=" + tagName);
HttpResponse<String> response = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build()
.send(HttpRequest.newBuilder(uri).GET().build(), HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
JsonNode manifestDigest = JsonSerialization.mapper.readTree(response.body()).findValue("manifest_digest");
if (manifestDigest != null) {
return "@" + manifestDigest.asText();
}
}
throw new RuntimeException(String.format("Unable to get manifest_digest for image with tag %s from quay.io. Response: %d, %s. ", tagName, response.statusCode(), response.body()));
} catch (URISyntaxException | IOException | InterruptedException ex) {
throw new RuntimeException(ex);
}
} else {
return ":" + tagName;
}
}
}

View file

@ -1,335 +0,0 @@
/*
* 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.testsuite.zerodowntime;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.Duration;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.common.util.Retry;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.CockroachdbContainerProvider;
import org.keycloak.testsuite.arquillian.HotRodContainerProvider;
import org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider;
import org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer;
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.COCKROACH;
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.HOTROD;
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.NONE;
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.POSTGRES;
import org.keycloak.testsuite.arquillian.PostgresContainerProvider;
import org.keycloak.testsuite.auth.page.AuthRealm;
import org.keycloak.testsuite.updaters.SetSystemProperty;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.keycloak.testsuite.util.GroupBuilder;
import org.keycloak.testsuite.util.InfinispanContainer;
import org.keycloak.testsuite.util.LegacyKeycloakContainer;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.testsuite.util.RolesBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.testcontainers.containers.CockroachContainer;
import org.testcontainers.containers.PostgreSQLContainer;
/**
* It tests that object stored by legacy version of keycloak could be read by
* current one, then object is modified, therefore stored in current version and
* then it's verified it could be read by legacy keycloak.
*
* See HOW-TO-RUN.md for some additional notes.
*
* @author vramik
*/
@Ignore
public class ZeroDowntimeTest extends AbstractKeycloakTest {
@ArquillianResource private ContainerController controller;
@ArquillianResource private LegacyKeycloakContainerProvider legacyKeycloakServerProvider;
@ArquillianResource private PostgresContainerProvider postgresProvider;
@ArquillianResource private CockroachdbContainerProvider cockroachdbProvider;
@ArquillianResource private HotRodContainerProvider hotrodProvider;
private final String legacyKeycloakHost = System.getProperty("keycloak.legacy.host", "localhost");
private final String legacyKeycloakPort = System.getProperty("keycloak.legacy.port", "8091");
private final String legacyKeycloakUrl = "http://" + legacyKeycloakHost + ":" + legacyKeycloakPort;
private String currentAuthServer;
private SetSystemProperty sysProp;
private static boolean initialized;
private static Keycloak legacyAdminClient;
// junit4 enforces to use static method when using @BeforeClass or @ClassRule
// TODO: after upgrade to junit5: replace @Before with @BeforeAll and get rid of the "initialized" flag
@Before
public void before() throws Exception {
Assume.assumeFalse(LegacyKeycloakContainerProvider.CONTAINER.equals(ZeroDowntimeContainer.NONE));
if (initialized) return;
currentAuthServer = suiteContext.getAuthServerInfo().getArquillianContainer().getName();
// stop current auth-server
if (controller.isStarted(currentAuthServer)) {
log.debug("Stopping auth server");
controller.stop(currentAuthServer);
}
// stop and create new container
handleContainer();
//start legacy container
LegacyKeycloakContainer legacyKeycloakServer = legacyKeycloakServerProvider.get();
legacyKeycloakServer.start();
legacyAdminClient = KeycloakBuilder.builder()
.serverUrl(legacyKeycloakUrl)
.realm(AuthRealm.MASTER)
.username(AuthRealm.ADMIN)
.password(AuthRealm.ADMIN)
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
.resteasyClient(AdminClientUtil.createResteasyClient())
.build();
// import test realm
legacyAdminClient.realms().create(RealmBuilder.create()
.name(AuthRealm.TEST)
.attribute("attr", "val")
.client(ClientBuilder.create()
.clientId("client"))
.clientScope(ClientScopeBuilder.create().name("client_scope"))
.roles(RolesBuilder.create()
.clientRole("client", RoleBuilder.create().name("client_role").singleAttribute("attr", "val").build())
.realmRole(RoleBuilder.create().name("realm_role").singleAttribute("attr", "val").build()))
.group(GroupBuilder.create().name("group").build())
.user(UserBuilder.create().username("user").addAttribute("attr", "val"))
.build());
// start current auth-server
controller.start(currentAuthServer);
reconnectAdminClient();
testContext.registerAfterClassAction(() -> {
legacyKeycloakServer.stop();
if (sysProp != null) sysProp.revert();
});
initialized = true;
}
private void handleContainer() {
switch (LegacyKeycloakContainerProvider.CONTAINER) {
case POSTGRES:
handlePostgres();
break;
case COCKROACH:
handleCockroach();
break;
case HOTROD:
handleHotRod();
break;
case NONE:
default:
throw new IllegalStateException("Unknown container!");
}
}
private void handlePostgres() {
PostgreSQLContainer postgres = postgresProvider.getContainer();
//if suite container is running stop it
if (postgres != null && postgres.isRunning()) {
postgres.stop();
}
if (Boolean.parseBoolean(System.getProperty("postgres.start-container-for-zero-downtime", "true"))) {
postgres = postgresProvider.createContainer();
postgres.start();
log.infof("DatabaseInfo: %s, user=%s, pass=%s", postgres.getJdbcUrl(), PostgresContainerProvider.POSTGRES_DB_USER, PostgresContainerProvider.POSTGRES_DB_PASSWORD);
sysProp = new SetSystemProperty("keycloak.map.storage.connectionsJpa.url", postgres.getJdbcUrl());
}
}
private void handleCockroach() {
final CockroachContainer cockroach = cockroachdbProvider.getContainer();
//if suite container is running stop it
if (cockroach != null && cockroach.isRunning()) {
cockroach.stop();
//it needs some time to stop
Retry.executeWithBackoff(i -> {
if (cockroach.isRunning()) {
throw new AssertionError(String.format("Stopping CockroachDB container was not successful. Number of attempts: %d", i));
}
}, Duration.ofMinutes(1), 50);
}
// spawn new container
if (Boolean.parseBoolean(System.getProperty("cockroachdb.start-container-for-zero-downtime", "true"))) {
CockroachContainer newCockroach = cockroachdbProvider.createContainer();
newCockroach.withStartupAttempts(3); // with podman it sometimes fails with com.sun.jna.LastErrorException: https://github.com/testcontainers/testcontainers-java/issues/6640
newCockroach.start();
log.infof("DatabaseInfo: %s, user=%s, pass=%s", newCockroach.getJdbcUrl(), CockroachdbContainerProvider.COCKROACHDB_DB_USER, CockroachdbContainerProvider.COCKROACHDB_DB_PASSWORD);
sysProp = new SetSystemProperty("keycloak.map.storage.connectionsJpa.url", newCockroach.getJdbcUrl());
/*
* CRDB container fails to connect intermitently with:
* org.postgresql.util.PSQLException: FATAL: password authentication failed for user "keycloak"
*
* It seems it needs some time to set correct permission for a user after fresh start
* Waiting for successful connection.
*/
Retry.executeWithBackoff(i -> {
try {
DriverManager.getConnection(newCockroach.getJdbcUrl(), CockroachdbContainerProvider.COCKROACHDB_DB_USER, CockroachdbContainerProvider.COCKROACHDB_DB_PASSWORD);
} catch (SQLException e) {
throw new AssertionError(String.format("Establishing connection was not successful. Number of attempts: %d", i));
}
}, Duration.ofMinutes(1), 500);
}
}
private void handleHotRod() {
InfinispanContainer hotRodContainer = hotrodProvider.getContainer();
//if suite container is running stop it
if (hotRodContainer != null && hotRodContainer.isRunning()) {
hotRodContainer.stop();
}
// spawn new container
if (Boolean.parseBoolean(System.getProperty("start-hotrod-container-for-zero-downtime", "true"))) {
hotRodContainer = new InfinispanContainer();
hotRodContainer.start();
sysProp = new SetSystemProperty(HotRodContainerProvider.HOT_ROD_STORE_HOST_PROPERTY, hotRodContainer.getHost());
}
}
@Test
public void realmSmokeTest() throws Exception {
//realm
RealmRepresentation testRealmRep = adminClient.realm(AuthRealm.TEST).toRepresentation();
String realmAttribute = testRealmRep.getAttributes().get("attr");
assertThat(realmAttribute, equalTo("val"));
adminClient.realm(AuthRealm.TEST).update(RealmBuilder.edit(testRealmRep).displayName("displayName").build());
//verify objects can be read by legacy server
RealmRepresentation returnedRealmRep = legacyAdminClient.realm(AuthRealm.TEST).toRepresentation();
assertThat(returnedRealmRep.getDisplayName(), is("displayName"));
assertThat(returnedRealmRep.getAttributes().get("attr"), is("val"));
}
@Test
public void clientSmokeTest () throws Exception {
//client
List<ClientRepresentation> clients = adminClient.realm(AuthRealm.TEST).clients().findByClientId("client");
assertThat(clients, hasSize(1));
ClientRepresentation clientRep = clients.get(0);
adminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).update(ClientBuilder.edit(clientRep).addRedirectUri("*").build());
//client role
List<RoleRepresentation> clientRoles = adminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).roles().list();
assertThat(clientRoles, hasSize(1));
RoleRepresentation clientRoleRep = clientRoles.get(0);
assertThat(clientRoleRep.getName(), is("client_role"));
adminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).roles().get(clientRoleRep.getName()).update(RoleBuilder.edit(clientRoleRep).description("desc").build());
//verify objects can be read by legacy server
ClientRepresentation returnedClientRep = legacyAdminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).toRepresentation();
assertThat(returnedClientRep.getRedirectUris(), hasItem("*"));
RoleRepresentation returnedClientRoleRep = legacyAdminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).roles().get(clientRoleRep.getName()).toRepresentation();
assertThat(returnedClientRoleRep.getDescription(), is("desc"));
assertThat(returnedClientRoleRep.getAttributes().get("attr"), hasItem("val"));
}
@Test
public void clientScopeSmokeTest () throws Exception {
//client scope
ClientScopeRepresentation clientScopeRep = adminClient.realm(AuthRealm.TEST).clientScopes().findAll().stream().filter(scope -> scope.getName().equals("client_scope")).findFirst().orElse(null);
assertThat(clientScopeRep, notNullValue());
adminClient.realm(AuthRealm.TEST).clientScopes().get(clientScopeRep.getId()).update(ClientScopeBuilder.edit(clientScopeRep).description("desc").build());
//verify objects can be read by legacy server
ClientScopeRepresentation returnedClientScopeRep = legacyAdminClient.realm(AuthRealm.TEST).clientScopes().get(clientScopeRep.getId()).toRepresentation();
assertThat(returnedClientScopeRep.getDescription(), is("desc"));
}
@Test
public void userRoleSmokeTest() throws Exception {
//user
List<UserRepresentation> users = adminClient.realm(AuthRealm.TEST).users().searchByUsername("user", Boolean.TRUE);
assertThat(users, hasSize(1));
UserRepresentation userRep = users.get(0);
adminClient.realm(AuthRealm.TEST).users().get(userRep.getId()).update(UserBuilder.edit(userRep).email("test@test").build());
//verify objects can be read by legacy server
UserRepresentation returnedUserRep = legacyAdminClient.realm(AuthRealm.TEST).users().get(userRep.getId()).toRepresentation();
assertThat(returnedUserRep.getEmail(), is("test@test"));
assertThat(returnedUserRep.getAttributes().get("attr"), hasItem("val"));
}
@Test
public void roleSmokeTest() throws Exception {
//realm role
RoleRepresentation realmRoleRep = adminClient.realm(AuthRealm.TEST).roles().get("realm_role").toRepresentation();
adminClient.realm(AuthRealm.TEST).roles().get("realm_role").update(RoleBuilder.edit(realmRoleRep).description("desc").build());
//verify objects can be read by legacy server
RoleRepresentation returnedRoleRep = legacyAdminClient.realm(AuthRealm.TEST).roles().get(realmRoleRep.getName()).toRepresentation();
assertThat(returnedRoleRep.getDescription(), is("desc"));
assertThat(returnedRoleRep.getAttributes().get("attr"), hasItem("val"));
}
@Test
public void groupSmokeTest() throws Exception {
//group
List<GroupRepresentation> groups = adminClient.realm(AuthRealm.TEST).groups().groups();
assertThat(groups, hasSize(1));
GroupRepresentation groupRep = groups.get(0);
adminClient.realm(AuthRealm.TEST).groups().group(groupRep.getId()).update(GroupBuilder.edit(groupRep).singleAttribute("attr", "val").build());
//verify objects can be read by legacy server
GroupRepresentation returnedGroupRep = legacyAdminClient.realm(AuthRealm.TEST).groups().group(groupRep.getId()).toRepresentation();
assertThat(returnedGroupRep.getAttributes().get("attr"), hasItem("val"));
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}
}

View file

@ -45,4 +45,3 @@ validation,6
vault,4
welcomepage,6
x509,4
zerodowntime,1

View file

@ -17,4 +17,3 @@ TransactionsTest
UserProfileTest
org.keycloak.testsuite.admin.**
org.keycloak.testsuite.authz.**ManagementTest
org.keycloak.testsuite.zerodowntime.**