mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Default jdbc-ping cluster setup for distributed caches fails in Oracle
* Add DatabaseConfig to TestDatabase so the underlying DB can be configured per test * Allow DB initScripts to be configured by tests Closes #40784 Closes #41105 Signed-off-by: Ryan Emerson <remerson@redhat.com> Signed-off-by: Alexander Schwartz <aschwart@redhat.com> Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
85b494ec51
commit
52a83509dc
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -708,6 +708,11 @@ jobs:
|
||||
|
||||
- name: Database container port
|
||||
run: |
|
||||
# The Ryuk container process exists temporarily after the JVM terminates, wait for only the database container to remain
|
||||
while [ "$(docker ps -q | wc -l)" -ne 1 ]; do
|
||||
docker ps
|
||||
sleep 10
|
||||
done
|
||||
DATABASE_PORT=$(docker ps -l --format '{{ .ID }}' | xargs docker port | cut -d ':' -f 2)
|
||||
echo "DATABASE_PORT=$DATABASE_PORT" >> $GITHUB_ENV
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ package org.keycloak.connections.jpa.util;
|
||||
import jakarta.persistence.PersistenceUnitTransactionType;
|
||||
import jakarta.persistence.ValidationMode;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
|
||||
import org.hibernate.internal.SessionFactoryImpl;
|
||||
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
|
||||
import org.hibernate.jpa.boot.spi.PersistenceXmlParser;
|
||||
@ -32,6 +33,7 @@ import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
@ -56,9 +58,10 @@ public class JpaUtils {
|
||||
private static final Logger logger = Logger.getLogger(JpaUtils.class);
|
||||
|
||||
public static String getTableNameForNativeQuery(String tableName, EntityManager em) {
|
||||
String schema = (String) em.getEntityManagerFactory().getProperties().get(HIBERNATE_DEFAULT_SCHEMA);
|
||||
final Dialect dialect = em.getEntityManagerFactory().unwrap(SessionFactoryImpl.class).getJdbcServices().getDialect();
|
||||
return (schema==null) ? tableName : dialect.openQuote() + schema + dialect.closeQuote() + "." + tableName;
|
||||
IdentifierHelper identifierHelper = em.getEntityManagerFactory().unwrap(SessionFactoryImpl.class).getJdbcServices().getJdbcEnvironment().getIdentifierHelper();
|
||||
String schema = em.getEntityManagerFactory().unwrap(SessionFactoryImpl.class).getSessionFactoryOptions().getDefaultSchema();
|
||||
return (schema==null) ? tableName : identifierHelper.toIdentifier(schema).render(dialect) + "." + tableName;
|
||||
}
|
||||
|
||||
private static List<ParsedPersistenceXmlDescriptor> transformPersistenceUnits(Collection<PersistenceUnitDescriptor> descriptors) {
|
||||
|
||||
@ -349,6 +349,7 @@ Valid values:
|
||||
| mariadb | MariaDB test container |
|
||||
| mssql | Microsoft SQL Server test container |
|
||||
| mysql | MySQL test container |
|
||||
| oracle | Oracle test container |
|
||||
| postgres | PostgreSQL test container |
|
||||
|
||||
Configuration:
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package org.keycloak.testframework.annotations;
|
||||
|
||||
import org.keycloak.testframework.database.DatabaseConfigurator;
|
||||
import org.keycloak.testframework.database.DefaultDatabaseConfigurator;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
@ -13,4 +15,5 @@ public @interface InjectTestDatabase {
|
||||
|
||||
LifeCycle lifecycle() default LifeCycle.GLOBAL;
|
||||
|
||||
Class<? extends DatabaseConfigurator> config() default DefaultDatabaseConfigurator.class;
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ public class Config {
|
||||
}
|
||||
|
||||
public static <T> T getValueTypeConfig(Class<?> valueType, String name, String defaultValue, Class<T> type) {
|
||||
name = "kc.test." + valueTypeAlias.getAlias(valueType) + "." + name;
|
||||
name = getValueTypeFQN(valueType, name);
|
||||
Optional<T> optionalValue = config.getOptionalValue(name, type);
|
||||
if (optionalValue.isPresent()) {
|
||||
return optionalValue.get();
|
||||
@ -42,11 +42,15 @@ public class Config {
|
||||
}
|
||||
}
|
||||
public static <T> T getValueTypeConfig(Class<?> valueType, String name, T defaultValue, Class<T> type) {
|
||||
name = "kc.test." + valueTypeAlias.getAlias(valueType) + "." + name;
|
||||
name = getValueTypeFQN(valueType, name);
|
||||
Optional<T> optionalValue = config.getOptionalValue(name, type);
|
||||
return optionalValue.orElse(defaultValue);
|
||||
}
|
||||
|
||||
public static String getValueTypeFQN(Class<?> valueType, String name) {
|
||||
return "kc.test." + valueTypeAlias.getAlias(valueType) + "." + name;
|
||||
}
|
||||
|
||||
public static <T> T get(String name, T defaultValue, Class<T> clazz) {
|
||||
return config.getOptionalValue(name, clazz).orElse(defaultValue);
|
||||
}
|
||||
|
||||
@ -1,30 +1,39 @@
|
||||
package org.keycloak.testframework.database;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.testframework.config.Config;
|
||||
import org.keycloak.testframework.logging.JBossLogConsumer;
|
||||
import org.testcontainers.containers.JdbcDatabaseContainer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.testframework.config.Config;
|
||||
import org.keycloak.testframework.logging.JBossLogConsumer;
|
||||
import org.testcontainers.containers.JdbcDatabaseContainer;
|
||||
|
||||
public abstract class AbstractContainerTestDatabase implements TestDatabase {
|
||||
|
||||
protected boolean reuse;
|
||||
|
||||
protected JdbcDatabaseContainer<?> container;
|
||||
protected DatabaseConfig config;
|
||||
|
||||
public AbstractContainerTestDatabase() {
|
||||
reuse = Config.getValueTypeConfig(TestDatabase.class, "reuse", false, Boolean.class);
|
||||
}
|
||||
public void start(DatabaseConfig config) {
|
||||
this.config = config;
|
||||
|
||||
String reuseProp = Config.getValueTypeFQN(TestDatabase.class, "reuse");
|
||||
boolean reuseConfigured = Config.get(reuseProp, false, Boolean.class);
|
||||
if (config.preventReuse() && reuseConfigured) {
|
||||
getLogger().warnf("Ignoring '%s' as test explicitly prevents it", reuseProp);
|
||||
this.reuse = false;
|
||||
} else {
|
||||
this.reuse = reuseConfigured;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
container = createContainer();
|
||||
container = container.withStartupTimeout(Duration.ofMinutes(10))
|
||||
.withLogConsumer(new JBossLogConsumer(Logger.getLogger("managed.db." + getDatabaseVendor())))
|
||||
.withReuse(reuse);
|
||||
.withReuse(reuse)
|
||||
.withInitScript(config.initScript());
|
||||
withDatabaseAndUser(getDatabase(), getUsername(), getPassword());
|
||||
container.start();
|
||||
|
||||
@ -73,7 +82,7 @@ public abstract class AbstractContainerTestDatabase implements TestDatabase {
|
||||
}
|
||||
|
||||
public String getDatabase() {
|
||||
return "keycloak";
|
||||
; return config.database() == null ? "keycloak" : config.database();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
@ -96,5 +105,4 @@ public abstract class AbstractContainerTestDatabase implements TestDatabase {
|
||||
public abstract String getDatabaseVendor();
|
||||
|
||||
public abstract Logger getLogger();
|
||||
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import org.keycloak.testframework.injection.InstanceContext;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
import org.keycloak.testframework.injection.RequestedInstance;
|
||||
import org.keycloak.testframework.injection.Supplier;
|
||||
import org.keycloak.testframework.injection.SupplierHelpers;
|
||||
import org.keycloak.testframework.injection.SupplierOrder;
|
||||
import org.keycloak.testframework.server.KeycloakServer;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
|
||||
@ -15,8 +16,15 @@ public abstract class AbstractDatabaseSupplier implements Supplier<TestDatabase,
|
||||
|
||||
@Override
|
||||
public TestDatabase getValue(InstanceContext<TestDatabase, InjectTestDatabase> instanceContext) {
|
||||
DatabaseConfigBuilder builder = DatabaseConfigBuilder
|
||||
.create()
|
||||
.withPreventReuse(instanceContext.getLifeCycle() != LifeCycle.GLOBAL);
|
||||
|
||||
DatabaseConfigurator configurator = SupplierHelpers.getInstance(instanceContext.getAnnotation().config());
|
||||
configurator.configure(builder);
|
||||
|
||||
TestDatabase testDatabase = getTestDatabase();
|
||||
testDatabase.start();
|
||||
testDatabase.start(builder.build());
|
||||
return testDatabase;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
package org.keycloak.testframework.database;
|
||||
|
||||
public record DatabaseConfig(String initScript, String database, boolean preventReuse) {
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.keycloak.testframework.database;
|
||||
|
||||
public class DatabaseConfigBuilder {
|
||||
private String initScript;
|
||||
private String database;
|
||||
private boolean preventReuse;
|
||||
|
||||
private DatabaseConfigBuilder() {}
|
||||
|
||||
public static DatabaseConfigBuilder create() {
|
||||
return new DatabaseConfigBuilder();
|
||||
}
|
||||
|
||||
public DatabaseConfigBuilder withInitScript(String initScript) {
|
||||
this.initScript = initScript;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DatabaseConfigBuilder withDatabase(String database) {
|
||||
this.database = database;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DatabaseConfigBuilder withPreventReuse(boolean preventReuse) {
|
||||
this.preventReuse = preventReuse;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DatabaseConfig build() {
|
||||
return new DatabaseConfig(initScript, database, preventReuse);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package org.keycloak.testframework.database;
|
||||
|
||||
public interface DatabaseConfigurator {
|
||||
DatabaseConfigBuilder configure(DatabaseConfigBuilder builder);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package org.keycloak.testframework.database;
|
||||
|
||||
public class DefaultDatabaseConfigurator implements DatabaseConfigurator {
|
||||
@Override
|
||||
public DatabaseConfigBuilder configure(DatabaseConfigBuilder builder) {
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,9 @@ public class DevFileDatabaseSupplier extends AbstractDatabaseSupplier {
|
||||
private static class DevFileTestDatabase implements TestDatabase {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
public void start(DatabaseConfig config) {
|
||||
if (config.initScript() != null)
|
||||
throw new IllegalArgumentException("init script not supported, configure h2 properties via --db-url-properties");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -17,7 +17,9 @@ public class DevMemDatabaseSupplier extends AbstractDatabaseSupplier {
|
||||
private static class DevMemTestDatabase implements TestDatabase {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
public void start(DatabaseConfig config) {
|
||||
if (config.initScript() != null)
|
||||
throw new IllegalArgumentException("init script not supported, configure h2 properties via --db-url-properties");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -4,10 +4,9 @@ import java.util.Map;
|
||||
|
||||
public interface TestDatabase {
|
||||
|
||||
void start();
|
||||
void start(DatabaseConfig databaseConfig);
|
||||
|
||||
void stop();
|
||||
|
||||
Map<String, String> serverConfig();
|
||||
|
||||
}
|
||||
|
||||
@ -5,12 +5,14 @@ import org.testcontainers.containers.JdbcDatabaseContainer;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
class PostgresTestDatabase extends AbstractContainerTestDatabase {
|
||||
public class PostgresTestDatabase extends AbstractContainerTestDatabase {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PostgresTestDatabase.class);
|
||||
|
||||
public static final String NAME = "postgres";
|
||||
|
||||
PostgresTestDatabase() {}
|
||||
|
||||
@Override
|
||||
public JdbcDatabaseContainer<?> createContainer() {
|
||||
return new PostgreSQLContainer<>(DockerImageName.parse(DatabaseProperties.getContainerImageName(NAME)).asCompatibleSubstituteFor(NAME));
|
||||
|
||||
@ -133,6 +133,16 @@
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
package org.keycloak.tests.db;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
|
||||
import org.keycloak.admin.client.resource.RolesResource;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectClient;
|
||||
import org.keycloak.testframework.annotations.InjectTestDatabase;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.config.Config;
|
||||
import org.keycloak.testframework.database.DatabaseConfigBuilder;
|
||||
import org.keycloak.testframework.database.PostgresTestDatabase;
|
||||
import org.keycloak.testframework.database.TestDatabase;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
import org.keycloak.testframework.realm.ManagedClient;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfig;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
|
||||
import org.keycloak.testsuite.util.RoleBuilder;
|
||||
|
||||
@DisabledIfEnvironmentVariable(named = "KC_TEST_DATABASE", matches = "mssql", disabledReason = "MSSQL does not support setting the default schema per session")
|
||||
@KeycloakIntegrationTest(config = CaseSensitiveSchemaTest.KeycloakConfig.class)
|
||||
public class CaseSensitiveSchemaTest {
|
||||
@InjectTestDatabase(lifecycle = LifeCycle.CLASS, config = DatabaseConfigurator.class)
|
||||
TestDatabase db;
|
||||
|
||||
@InjectClient
|
||||
ManagedClient managedClient;
|
||||
|
||||
@Test
|
||||
public void testCaseSensitiveSchema() {
|
||||
RoleRepresentation role1 = RoleBuilder.create()
|
||||
.name("role1")
|
||||
.description("role1-description")
|
||||
.singleAttribute("role1-attr-key", "role1-attr-val")
|
||||
.build();
|
||||
|
||||
RolesResource roles = managedClient.admin().roles();
|
||||
roles.create(role1);
|
||||
roles.deleteRole(role1.getName());
|
||||
}
|
||||
|
||||
protected static String dbType() {
|
||||
String database = Config.getSelectedSupplier(TestDatabase.class);
|
||||
return database == null ? "dev-mem" : database;
|
||||
}
|
||||
|
||||
public static class KeycloakConfig implements KeycloakServerConfig {
|
||||
@Override
|
||||
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
|
||||
|
||||
return switch (dbType()) {
|
||||
// DBs that convert unquoted to lower-case by default
|
||||
case PostgresTestDatabase.NAME -> config.option("db-schema", "KEYCLOAK");
|
||||
// DBs that convert unquoted to upper-case by default
|
||||
case "dev-file", "dev-mem" -> config.option("db-url-properties", ";INIT=CREATE SCHEMA IF NOT EXISTS keycloak").option("db-schema", "keycloak");
|
||||
default -> config.option("db-schema", "keycloak");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseConfigurator implements org.keycloak.testframework.database.DatabaseConfigurator {
|
||||
@Override
|
||||
public DatabaseConfigBuilder configure(DatabaseConfigBuilder builder) {
|
||||
if (PostgresTestDatabase.NAME.equals(dbType())) {
|
||||
builder.withInitScript("org/keycloak/tests/db/case-sensitive-schema-postgres.sql");
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package org.keycloak.tests.db;
|
||||
|
||||
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
|
||||
import org.keycloak.testframework.annotations.InjectTestDatabase;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.database.DatabaseConfigBuilder;
|
||||
import org.keycloak.testframework.database.PostgresTestDatabase;
|
||||
import org.keycloak.testframework.database.TestDatabase;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfig;
|
||||
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
|
||||
|
||||
@DisabledIfEnvironmentVariable(named = "KC_TEST_DATABASE", matches = "mssql", disabledReason = "MSSQL does not support setting the default schema per session")
|
||||
@DisabledIfEnvironmentVariable(named = "KC_TEST_DATABASE", matches = "oracle", disabledReason = "Oracle image does not support configuring user/databases with '-'")
|
||||
@KeycloakIntegrationTest(config = PreserveSchemaCaseLiquibaseTest.KeycloakConfig.class)
|
||||
public class PreserveSchemaCaseLiquibaseTest extends CaseSensitiveSchemaTest {
|
||||
|
||||
@InjectTestDatabase(lifecycle = LifeCycle.CLASS, config = DatabaseConfigurator.class)
|
||||
TestDatabase db;
|
||||
|
||||
public static class KeycloakConfig implements KeycloakServerConfig {
|
||||
@Override
|
||||
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
|
||||
switch (dbType()) {
|
||||
case "dev-file":
|
||||
case "dev-mem":
|
||||
config.option("db-url-properties", ";INIT=CREATE SCHEMA IF NOT EXISTS \"keycloak-t\"");
|
||||
}
|
||||
return config.option("db-schema", "keycloak-t");
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseConfigurator implements org.keycloak.testframework.database.DatabaseConfigurator {
|
||||
@Override
|
||||
public DatabaseConfigBuilder configure(DatabaseConfigBuilder builder) {
|
||||
if (dbType().equals(PostgresTestDatabase.NAME)) {
|
||||
return builder.withInitScript("org/keycloak/tests/db/preserve-schema-case-liquibase-postgres.sql");
|
||||
}
|
||||
return builder.withDatabase("keycloak-t");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
CREATE SCHEMA KEYCLOAK;
|
||||
@ -0,0 +1 @@
|
||||
CREATE SCHEMA "keycloak-t";
|
||||
@ -49,6 +49,12 @@
|
||||
<groupId>org.keycloak.testframework</groupId>
|
||||
<artifactId>keycloak-test-framework-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak.tests</groupId>
|
||||
<artifactId>keycloak-tests-base</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package org.keycloak.tests.clustering;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.testframework.annotations.InjectTestDatabase;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.database.TestDatabase;
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
import org.keycloak.tests.db.CaseSensitiveSchemaTest;
|
||||
|
||||
@KeycloakIntegrationTest(config = CaseSensitiveSchemaTest.KeycloakConfig.class)
|
||||
public class JdbcPingCustomSchemaTest {
|
||||
@InjectTestDatabase(lifecycle = LifeCycle.CLASS, config = CaseSensitiveSchemaTest.DatabaseConfigurator.class)
|
||||
TestDatabase db;
|
||||
|
||||
@Test
|
||||
public void testClusterFormed() {
|
||||
// no-op ClusteredKeycloakServer will fail if a cluster is not formed
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user