Change tx driver handling.

Introduce a non-tx driver for the vendors and map based on new build option transaction-tx-enabled

Closes #10191 and others.

Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Dominik Guhr 2022-02-17 12:52:20 +01:00 committed by Pedro Igor
parent 9fd86ac27f
commit 19a17e79ba
11 changed files with 125 additions and 51 deletions

View file

@ -5,7 +5,7 @@
<@tmpl.guide
title="Configuring the database"
summary="An overview about how to configure relational databases"
includedOptions="db db-*">
includedOptions="db db-* transaction-xa-enabled">
In this guide, you are going to understand how to configure the server to store data using different relational databases. You should also learn
You will also learn what databases are supported by the server.
@ -119,4 +119,11 @@ By default, the maximum timeout for this lock is 900 seconds. If a node is waiti
<@kc.start parameters="--spi-dblock-jpa-lock-wait-timeout 900"/>
== Using Database Vendors without XA transaction support
Keycloak uses XA transactions and the appropriate database drivers by default. There are vendors like Azure SQL and MariaDB Galera, that do not support or rely on the XA transaction mechanism. To use Keycloak without XA transaction support using the appropriate jdbc driver, invoke the following command:
<@kc.build parameters="--db=<vendor> --transaction-xa-enabled=false"/>
Keycloak will automatically choose the appropriate jdbc driver for your vendor.
</@tmpl.guide>

View file

@ -4,13 +4,14 @@ public enum ConfigCategory {
// ordered by name asc
CLUSTERING("Cluster", 10),
DATABASE("Database", 20),
FEATURE("Feature", 30),
HOSTNAME("Hostname", 40),
HTTP("HTTP/TLS", 50),
METRICS("Metrics", 60),
PROXY("Proxy", 70),
VAULT("Vault", 80),
LOGGING("Logging", 90),
TRANSACTION("Transaction",30),
FEATURE("Feature", 40),
HOSTNAME("Hostname", 50),
HTTP("HTTP/TLS", 60),
METRICS("Metrics", 70),
PROXY("Proxy", 80),
VAULT("Vault", 90),
LOGGING("Logging", 100),
GENERAL("General", 999);
private final String heading;

View file

@ -2,8 +2,10 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
import org.keycloak.quarkus.runtime.storage.database.Database;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
@ -26,9 +28,9 @@ final class DatabasePropertyMappers {
.build(),
builder().from("db-driver")
.mapFrom("db")
.defaultValue(Database.getDriver("dev-file").get())
.defaultValue(Database.getDriver("dev-file", true).get())
.to("quarkus.datasource.jdbc.driver")
.transformer((db, context) -> Database.getDriver(db).orElse(db))
.transformer(DatabasePropertyMappers::getXaOrNonXaDriver)
.hidden(true)
.build(),
builder().from("db").
@ -39,11 +41,6 @@ final class DatabasePropertyMappers {
.paramLabel("vendor")
.expectedValues(asList(Database.getAliases()))
.build(),
builder().from("db-tx-type")
.defaultValue("xa")
.to("quarkus.datasource.jdbc.transactions")
.hidden(true)
.build(),
builder().from("db-url")
.to("quarkus.datasource.jdbc.url")
.mapFrom("db")
@ -106,6 +103,14 @@ final class DatabasePropertyMappers {
};
}
private static String getXaOrNonXaDriver(String db, ConfigSourceInterceptorContext context) {
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
boolean isXaEnabled = xaEnabledConfigValue == null || Boolean.parseBoolean(xaEnabledConfigValue.getValue());
return Database.getDriver(db, isXaEnabled).orElse(db);
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> toDatabaseKind() {
return (db, context) -> {
Optional<String> databaseKind = Database.getDatabaseKind(db);

View file

@ -31,6 +31,7 @@ public final class PropertyMappers {
MAPPERS.addAll(VaultPropertyMappers.getVaultPropertyMappers());
MAPPERS.addAll(FeaturePropertyMappers.getMappers());
MAPPERS.addAll(LoggingPropertyMappers.getMappers());
MAPPERS.addAll(TransactionPropertyMappers.getTransactionPropertyMappers());
}
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {

View file

@ -0,0 +1,39 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import java.util.Arrays;
public class TransactionPropertyMappers {
private TransactionPropertyMappers(){}
public static PropertyMapper[] getTransactionPropertyMappers() {
return new PropertyMapper[] {
builder().from("transaction-xa-enabled")
.to("quarkus.datasource.jdbc.transactions")
.defaultValue(Boolean.TRUE.toString())
.description("Manually override the transaction type. Transaction type XA and the appropriate driver is used by default.")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
.isBuildTimeProperty(true)
.transformer(TransactionPropertyMappers::getQuarkusTransactionsValue)
.build(),
};
}
private static String getQuarkusTransactionsValue(String txValue, ConfigSourceInterceptorContext context) {
boolean isXaEnabled = Boolean.parseBoolean(txValue);
if (isXaEnabled) {
return "xa";
}
return "enabled";
}
private static PropertyMapper.Builder builder() {
return PropertyMapper.builder(ConfigCategory.TRANSACTION);
}
}

View file

@ -68,14 +68,18 @@ public final class Database {
return Optional.of(vendor.defaultUrl.apply(alias));
}
public static Optional<String> getDriver(String alias) {
public static Optional<String> getDriver(String alias, boolean isXaEnabled) {
Vendor vendor = DATABASES.get(alias);
if (vendor == null) {
return Optional.empty();
}
return Optional.of(vendor.driver);
if (isXaEnabled) {
return Optional.of(vendor.xaDriver);
}
return Optional.of(vendor.nonXaDriver);
}
public static Optional<String> getDialect(String alias) {
@ -95,6 +99,7 @@ public final class Database {
private enum Vendor {
H2("h2",
"org.h2.jdbcx.JdbcDataSource",
"org.h2.Driver",
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
new Function<String, String>() {
@Override
@ -112,19 +117,21 @@ public final class Database {
),
MYSQL("mysql",
"com.mysql.cj.jdbc.MysqlXADataSource",
"com.mysql.cj.jdbc.Driver",
"org.hibernate.dialect.MySQL8Dialect",
"jdbc:mysql://${kc.db-url-host:localhost}/${kc.db-url-database:keycloak}${kc.db-url-properties:}",
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase")
),
MARIADB("mariadb",
"org.mariadb.jdbc.MySQLDataSource",
"org.mariadb.jdbc.Driver",
"org.hibernate.dialect.MariaDBDialect",
"jdbc:mariadb://${kc.db-url-host:localhost}/${kc.db-url-database:keycloak}${kc.db-url-properties:}",
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase")
),
POSTGRES("postgresql",
"org.postgresql.xa.PGXADataSource",
"org.postgresql.Driver",
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect",
"jdbc:postgresql://${kc.db-url-host:localhost}/${kc.db-url-database:keycloak}${kc.db-url-properties:}",
asList("liquibase.database.core.PostgresDatabase",
@ -133,6 +140,7 @@ public final class Database {
),
MSSQL("mssql",
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource",
"com.microsoft.sqlserver.jdbc.SQLServerDriver",
"org.hibernate.dialect.SQLServer2016Dialect",
"jdbc:sqlserver://${kc.db-url-host:localhost}:1433;databaseName=${kc.db-url-database:keycloak}${kc.db-url-properties:}",
asList("org.keycloak.quarkus.runtime.storage.database.liquibase.database.CustomMSSQLDatabase"),
@ -140,38 +148,36 @@ public final class Database {
),
ORACLE("oracle",
"oracle.jdbc.xa.client.OracleXADataSource",
"oracle.jdbc.driver.OracleDriver",
"org.hibernate.dialect.Oracle12cDialect",
"jdbc:oracle:thin:@//${kc.db-url-host:localhost}:1521/${kc.db-url-database:keycloak}",
asList("liquibase.database.core.OracleDatabase")
);
final String databaseKind;
final String driver;
final String xaDriver;
final String nonXaDriver;
final Function<String, String> dialect;
final Function<String, String> defaultUrl;
final List<String> liquibaseTypes;
final String[] aliases;
Vendor(String databaseKind, String driver, String dialect, String defaultUrl, List<String> liquibaseTypes,
String... aliases) {
this(databaseKind, driver, alias -> dialect, alias -> defaultUrl, liquibaseTypes, aliases);
Vendor(String databaseKind, String xaDriver, String nonXaDriver, String dialect, String defaultUrl, List<String> liquibaseTypes,
String... aliases) {
this(databaseKind, xaDriver, nonXaDriver, alias -> dialect, alias -> defaultUrl, liquibaseTypes, aliases);
}
Vendor(String databaseKind, String driver, String dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes, String... aliases) {
this(databaseKind, driver, alias -> dialect, defaultUrl, liquibaseTypes, aliases);
Vendor(String databaseKind, String xaDriver, String nonXaDriver, String dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes, String... aliases) {
this(databaseKind, xaDriver, nonXaDriver, alias -> dialect, defaultUrl, liquibaseTypes, aliases);
}
Vendor(String databaseKind, String driver, Function<String, String> dialect, String defaultUrl,
List<String> liquibaseTypes, String... aliases) {
this(databaseKind, driver, dialect, alias -> defaultUrl, liquibaseTypes, aliases);
}
Vendor(String databaseKind, String driver, Function<String, String> dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes,
String... aliases) {
Vendor(String databaseKind, String xaDriver, String nonXaDriver, Function<String, String> dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes,
String... aliases) {
this.databaseKind = databaseKind;
this.driver = driver;
this.xaDriver = xaDriver;
this.nonXaDriver = nonXaDriver;
this.dialect = dialect;
this.defaultUrl = defaultUrl;
this.liquibaseTypes = liquibaseTypes;

View file

@ -380,7 +380,7 @@ public class ConfigurationTest {
public void testDatabaseDriverSetExplicitly() {
System.setProperty(CLI_ARGS, "--db=mssql" + ARG_SEPARATOR + "--db-url=jdbc:sqlserver://localhost/keycloak");
System.setProperty("kc.db-driver", "com.microsoft.sqlserver.jdbc.SQLServerDriver");
System.setProperty("kc.db-tx-type", "enabled");
System.setProperty("kc.transaction-xa-enabled", "false");
assertTrue(System.getProperty(CLI_ARGS, "").contains("mssql"));
SmallRyeConfig config = createConfig();
assertEquals("jdbc:sqlserver://localhost/keycloak", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
@ -389,6 +389,23 @@ public class ConfigurationTest {
assertEquals("enabled", config.getConfigValue("quarkus.datasource.jdbc.transactions").getValue());
}
@Test
public void testTransactionTypeChangesDriver() {
System.setProperty(CLI_ARGS, "--db=mssql" + ARG_SEPARATOR + "--transaction-xa-enabled=false");
assertTrue(System.getProperty(CLI_ARGS, "").contains("mssql"));
SmallRyeConfig config = createConfig();
assertEquals("com.microsoft.sqlserver.jdbc.SQLServerDriver", config.getConfigValue("quarkus.datasource.jdbc.driver").getValue());
assertEquals("enabled", config.getConfigValue("quarkus.datasource.jdbc.transactions").getValue());
System.setProperty(CLI_ARGS, "--db=mssql" + ARG_SEPARATOR + "--transaction-xa-enabled=true");
assertTrue(System.getProperty(CLI_ARGS, "").contains("mssql"));
SmallRyeConfig config2 = createConfig();
assertEquals("com.microsoft.sqlserver.jdbc.SQLServerXADataSource", config2.getConfigValue("quarkus.datasource.jdbc.driver").getValue());
assertEquals("xa", config2.getConfigValue("quarkus.datasource.jdbc.transactions").getValue());
}
@Test
public void testResolveMetricsOption() {
System.setProperty(CLI_ARGS, "--metrics-enabled=true");

View file

@ -36,7 +36,7 @@ public class MSSQLStartDatabaseTest extends AbstractStartDabataseTest {
*/
@Override
@Test
@Launch({ "-Dkc.db-tx-type=enabled", "-Dkc.db-driver=com.microsoft.sqlserver.jdbc.SQLServerDriver", "start-dev" })
@Launch({ "--transaction-xa-enabled=false", "start-dev" })
void testSuccessful(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertStartedDevMode();

View file

@ -28,21 +28,7 @@ import io.quarkus.test.junit.main.LaunchResult;
public class CustomTransactionDistTest {
@Test
@Launch({ "-Dkc.db-tx-type=enabled", "-Dkc.db-driver=org.postgresql.xa.PGXADataSource", "build", "--db=postgres" })
void failNoXAUsingXADriver(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertError("Driver org.postgresql.xa.PGXADataSource is an XA datasource, but XA transactions have not been enabled on the default datasource");
}
@Test
@Launch({ "-Dkc.db-driver=com.microsoft.sqlserver.jdbc.SQLServerDriver", "build", "--db=mssql" })
void failXAUsingNonXADriver(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertError("Driver is not an XA dataSource, while XA has been enabled in the configuration of the default datasource");
}
@Test
@Launch({ "-Dkc.db-tx-type=enabled", "-Dkc.db-driver=com.microsoft.sqlserver.jdbc.SQLServerDriver", "build", "--db=mssql" })
@Launch({ "build", "--db=mssql", "--transaction-xa-enabled=false" })
void testNoXa(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertBuild();

View file

@ -39,6 +39,12 @@ Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
mysql, oracle, postgres
Transaction:
--transaction-xa-enabled <true|false>
Manually override the transaction type. Transaction type XA and the
appropriate driver is used by default. Default: true.
Feature:
--features <feature> Enables a set of one or more features.

View file

@ -52,6 +52,12 @@ Database:
--db-username <username>
The username of the database user.
Transaction:
--transaction-xa-enabled <true|false>
Manually override the transaction type. Transaction type XA and the
appropriate driver is used by default. Default: true.
Feature:
--features <feature> Enables a set of one or more features.