Add storage-jpa-db property into Quarkus. Distinguish postgres and crdb for jpa map store.

Closes #17305
This commit is contained in:
vramik 2023-01-19 19:22:26 +01:00 committed by Hynek Mlnařík
parent 97969e141c
commit 31e4c5cb7e
12 changed files with 119 additions and 25 deletions

View file

@ -20,7 +20,7 @@ public class DatabaseOptions {
.category(OptionCategory.DATABASE) .category(OptionCategory.DATABASE)
.description("The database vendor.") .description("The database vendor.")
.defaultValue("dev-file") .defaultValue("dev-file")
.expectedValues(Database::getAliases) .expectedValues(Database::getLegacyStoreAliases)
.buildTime(true) .buildTime(true)
.build(); .build();

View file

@ -27,6 +27,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.keycloak.config.database.Database;
public class StorageOptions { public class StorageOptions {
@ -333,6 +334,14 @@ public class StorageOptions {
.description("Root directory for file map store.") .description("Root directory for file map store.")
.build(); .build();
public static final Option<String> STORAGE_JPA_DB = new OptionBuilder<>("storage-jpa-db", String.class)
.category(OptionCategory.STORAGE)
.defaultValue(Database.Vendor.POSTGRES.name().toLowerCase())
.expectedValues(Database::getAvailableMapStoreAliases)
.description("The database vendor for jpa map storage.")
.buildTime(true)
.build();
private static String descriptionForStorageAreas(String areaAsText) { private static String descriptionForStorageAreas(String areaAsText) {
return "Sets a storage mechanism for " + areaAsText + "."; return "Sets a storage mechanism for " + areaAsText + ".";
} }
@ -344,4 +353,11 @@ public class StorageOptions {
private static List<String> getExpectedCacheNames() { private static List<String> getExpectedCacheNames() {
return Stream.concat(Stream.of("all"), AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getCacheName).distinct()).collect(Collectors.toList()); return Stream.concat(Stream.of("all"), AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getCacheName).distinct()).collect(Collectors.toList());
} }
public static Optional<Database.Vendor> getDatabaseVendor(String databaseKind) {
return Stream.of(Database.Vendor.values())
.filter(Database.Vendor::isEnabledOnNewStore)
.filter(v -> v.isOfKind(databaseKind))
.findFirst();
}
} }

View file

@ -22,9 +22,11 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@ -94,12 +96,31 @@ public final class Database {
return Optional.of(vendor.dialect.apply(alias)); return Optional.of(vendor.dialect.apply(alias));
} }
public static List<String> getAliases() { /**
return DATABASES.keySet().stream().sorted().collect(Collectors.toList()); * @return List of aliases of databases enabled on legacy store.
*/
public static List<String> getLegacyStoreAliases() {
return DATABASES.entrySet().stream()
.filter(e -> e.getValue().isEnabledOnLegacyStore())
.map(Entry::getKey)
.sorted()
.collect(Collectors.toList());
}
/**
* @return List of aliases of databases enabled on jpa map store.
*/
public static List<String> getAvailableMapStoreAliases() {
return Stream.of(Database.Vendor.values())
.filter(Database.Vendor::isEnabledOnNewStore)
.map(v -> v.aliases)// may be replaced by Database.Vendor::getAliases if we add the getter into Database.Vendor
.flatMap(Stream::of)
.collect(Collectors.toList());
} }
public enum Vendor { public enum Vendor {
H2("h2", H2("h2",
Enabled.LEGACY_ONLY,
"org.h2.jdbcx.JdbcDataSource", "org.h2.jdbcx.JdbcDataSource",
"org.h2.Driver", "org.h2.Driver",
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect", "io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
@ -146,6 +167,7 @@ public final class Database {
"dev-mem", "dev-file" "dev-mem", "dev-file"
), ),
MYSQL("mysql", MYSQL("mysql",
Enabled.LEGACY_ONLY,
"com.mysql.cj.jdbc.MysqlXADataSource", "com.mysql.cj.jdbc.MysqlXADataSource",
"com.mysql.cj.jdbc.Driver", "com.mysql.cj.jdbc.Driver",
"org.hibernate.dialect.MySQL8Dialect", "org.hibernate.dialect.MySQL8Dialect",
@ -153,6 +175,7 @@ public final class Database {
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase") asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase")
), ),
MARIADB("mariadb", MARIADB("mariadb",
Enabled.LEGACY_ONLY,
"org.mariadb.jdbc.MariaDbDataSource", "org.mariadb.jdbc.MariaDbDataSource",
"org.mariadb.jdbc.Driver", "org.mariadb.jdbc.Driver",
"org.hibernate.dialect.MariaDBDialect", "org.hibernate.dialect.MariaDBDialect",
@ -160,15 +183,25 @@ public final class Database {
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase") asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase")
), ),
POSTGRES("postgresql", POSTGRES("postgresql",
Enabled.ENABLED,
"org.postgresql.xa.PGXADataSource", "org.postgresql.xa.PGXADataSource",
"org.postgresql.Driver", "org.postgresql.Driver",
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect", "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect",
"jdbc:postgresql://${kc.db-url-host:localhost}:${kc.db-url-port:5432}/${kc.db-url-database:keycloak}${kc.db-url-properties:}", "jdbc:postgresql://${kc.db-url-host:localhost}:${kc.db-url-port:5432}/${kc.db-url-database:keycloak}${kc.db-url-properties:}",
asList("liquibase.database.core.PostgresDatabase", "liquibase.database.core.CockroachDatabase", asList("liquibase.database.core.PostgresDatabase", "org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase"),
"org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase"),
"postgres" "postgres"
), ),
COCKROACH(POSTGRES.databaseKind, //needs to be aligned with https://quarkus.io/guides/datasource#default-datasource
Enabled.MAP_STORE_ONLY,
POSTGRES.xaDriver,
POSTGRES.nonXaDriver,
"org.hibernate.dialect.CockroachDB201Dialect",
"jdbc:postgresql://${kc.db-url-host:localhost}:${kc.db-url-port:26257}/${kc.db-url-database:keycloak}${kc.db-url-properties:}",
List.of("liquibase.database.core.CockroachDatabase"),
"cockroach"
),
MSSQL("mssql", MSSQL("mssql",
Enabled.LEGACY_ONLY,
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource", "com.microsoft.sqlserver.jdbc.SQLServerXADataSource",
"com.microsoft.sqlserver.jdbc.SQLServerDriver", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"org.hibernate.dialect.SQLServer2016Dialect", "org.hibernate.dialect.SQLServer2016Dialect",
@ -177,6 +210,7 @@ public final class Database {
"mssql" "mssql"
), ),
ORACLE("oracle", ORACLE("oracle",
Enabled.LEGACY_ONLY,
"oracle.jdbc.xa.client.OracleXADataSource", "oracle.jdbc.xa.client.OracleXADataSource",
"oracle.jdbc.driver.OracleDriver", "oracle.jdbc.driver.OracleDriver",
"org.hibernate.dialect.Oracle12cDialect", "org.hibernate.dialect.Oracle12cDialect",
@ -185,6 +219,7 @@ public final class Database {
); );
final String databaseKind; final String databaseKind;
final Enabled enabled;
final String xaDriver; final String xaDriver;
final String nonXaDriver; final String nonXaDriver;
final Function<String, String> dialect; final Function<String, String> dialect;
@ -192,20 +227,21 @@ public final class Database {
final List<String> liquibaseTypes; final List<String> liquibaseTypes;
final String[] aliases; final String[] aliases;
Vendor(String databaseKind, String xaDriver, String nonXaDriver, String dialect, String defaultUrl, List<String> liquibaseTypes, Vendor(String databaseKind, Enabled enabled, String xaDriver, String nonXaDriver, String dialect, String defaultUrl, List<String> liquibaseTypes,
String... aliases) { String... aliases) {
this(databaseKind, xaDriver, nonXaDriver, alias -> dialect, alias -> defaultUrl, liquibaseTypes, aliases); this(databaseKind, enabled, xaDriver, nonXaDriver, alias -> dialect, alias -> defaultUrl, liquibaseTypes, aliases);
} }
Vendor(String databaseKind, String xaDriver, String nonXaDriver, String dialect, Function<String, String> defaultUrl, Vendor(String databaseKind, Enabled enabled, String xaDriver, String nonXaDriver, String dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes, String... aliases) { List<String> liquibaseTypes, String... aliases) {
this(databaseKind, xaDriver, nonXaDriver, alias -> dialect, defaultUrl, liquibaseTypes, aliases); this(databaseKind, enabled, xaDriver, nonXaDriver, alias -> dialect, defaultUrl, liquibaseTypes, aliases);
} }
Vendor(String databaseKind, String xaDriver, String nonXaDriver, Function<String, String> dialect, Function<String, String> defaultUrl, Vendor(String databaseKind, Enabled enabled, String xaDriver, String nonXaDriver, Function<String, String> dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes, List<String> liquibaseTypes,
String... aliases) { String... aliases) {
this.databaseKind = databaseKind; this.databaseKind = databaseKind;
this.enabled = enabled;
this.xaDriver = xaDriver; this.xaDriver = xaDriver;
this.nonXaDriver = nonXaDriver; this.nonXaDriver = nonXaDriver;
this.dialect = dialect; this.dialect = dialect;
@ -214,6 +250,14 @@ public final class Database {
this.aliases = aliases.length == 0 ? new String[] { databaseKind } : aliases; this.aliases = aliases.length == 0 ? new String[] { databaseKind } : aliases;
} }
public boolean isEnabledOnLegacyStore() {
return enabled.legacyStore;
}
public boolean isEnabledOnNewStore() {
return enabled.jpaMapStore;
}
public boolean isOfKind(String dbKind) { public boolean isOfKind(String dbKind) {
return databaseKind.equals(dbKind); return databaseKind.equals(dbKind);
} }
@ -223,4 +267,18 @@ public final class Database {
return databaseKind.toLowerCase(Locale.ROOT); return databaseKind.toLowerCase(Locale.ROOT);
} }
} }
private static class Enabled {
final static Enabled LEGACY_ONLY = new Enabled(true, false);
final static Enabled MAP_STORE_ONLY = new Enabled(false, true);
final static Enabled ENABLED = new Enabled(true, true);
final boolean legacyStore;
final boolean jpaMapStore;
private Enabled(boolean legacyStore, boolean jpaMapStore) {
this.legacyStore = legacyStore;
this.jpaMapStore = jpaMapStore;
}
}
} }

View file

@ -12,6 +12,7 @@ import java.util.Optional;
import static java.util.Optional.of; import static java.util.Optional.of;
import static org.keycloak.config.StorageOptions.STORAGE; import static org.keycloak.config.StorageOptions.STORAGE;
import static org.keycloak.config.StorageOptions.STORAGE_JPA_DB;
import static org.keycloak.quarkus.runtime.Messages.invalidDatabaseVendor; import static org.keycloak.quarkus.runtime.Messages.invalidDatabaseVendor;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawValue; import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawValue;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
@ -99,7 +100,7 @@ final class DatabasePropertyMappers {
if (url.isPresent()) { if (url.isPresent()) {
if (isJpaStore()) { if (isJpaStore()) {
return Database.getDefaultUrl(Database.Vendor.POSTGRES.name().toLowerCase()); return Database.getDefaultUrl(getJpaStoreDbVendor().name().toLowerCase());
} }
return url; return url;
} }
@ -109,7 +110,8 @@ final class DatabasePropertyMappers {
private static Optional<String> getXaOrNonXaDriver(Optional<String> value, ConfigSourceInterceptorContext context) { private static Optional<String> getXaOrNonXaDriver(Optional<String> value, ConfigSourceInterceptorContext context) {
if (isJpaStore()) { if (isJpaStore()) {
return Database.getDriver(Database.Vendor.POSTGRES.name().toLowerCase(), false); // always use XA driver with jpa map store
return Database.getDriver(getJpaStoreDbVendor().name().toLowerCase(), true);
} }
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled"); ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
@ -133,7 +135,7 @@ final class DatabasePropertyMappers {
private static Optional<String> toDatabaseKind(Optional<String> db, ConfigSourceInterceptorContext context) { private static Optional<String> toDatabaseKind(Optional<String> db, ConfigSourceInterceptorContext context) {
if (isJpaStore()) { if (isJpaStore()) {
return Database.getDatabaseKind(Database.Vendor.POSTGRES.name().toLowerCase()); return Database.getDatabaseKind(getJpaStoreDbVendor().name().toLowerCase());
} }
Optional<String> databaseKind = Database.getDatabaseKind(db.get()); Optional<String> databaseKind = Database.getDatabaseKind(db.get());
@ -142,14 +144,14 @@ final class DatabasePropertyMappers {
return databaseKind; return databaseKind;
} }
addInitializationException(invalidDatabaseVendor(db.get(), Database.getAliases())); addInitializationException(invalidDatabaseVendor(db.get(), Database.getLegacyStoreAliases()));
return of("h2"); return of("h2");
} }
private static Optional<String> resolveDatabaseVendor(Optional<String> db, ConfigSourceInterceptorContext context) { private static Optional<String> resolveDatabaseVendor(Optional<String> db, ConfigSourceInterceptorContext context) {
if (isJpaStore()) { if (isJpaStore()) {
return of(Database.Vendor.POSTGRES.name().toLowerCase()); return Optional.of(getJpaStoreDbVendor().name().toLowerCase());
} }
if (db.isEmpty()) { if (db.isEmpty()) {
@ -204,16 +206,13 @@ final class DatabasePropertyMappers {
return Database.getDialect("dev-file"); return Database.getDialect("dev-file");
} }
private static String getDefaultVendor() {
if (isJpaStore()) {
return Database.Vendor.POSTGRES.name().toLowerCase();
}
return "dev-file";
}
private static boolean isJpaStore() { private static boolean isJpaStore() {
String storage = getRawValue(NS_KEYCLOAK_PREFIX.concat(STORAGE.getKey())); String storage = getRawValue(NS_KEYCLOAK_PREFIX.concat(STORAGE.getKey()));
return storage != null && StorageOptions.StorageType.jpa.name().equals(storage); return storage != null && StorageOptions.StorageType.jpa.name().equals(storage);
} }
private static Database.Vendor getJpaStoreDbVendor() {
String storageJpaDb = getRawValue(NS_KEYCLOAK_PREFIX.concat(STORAGE_JPA_DB.getKey()));
return StorageOptions.getDatabaseVendor(storageJpaDb).orElse(Database.Vendor.POSTGRES);
}
} }

View file

@ -303,6 +303,11 @@ final class StoragePropertyMappers {
.to("kc.spi-map-storage-file-dir") .to("kc.spi-map-storage-file-dir")
.mapFrom("storage") .mapFrom("storage")
.paramLabel("dir") .paramLabel("dir")
.build(),
fromOption(StorageOptions.STORAGE_JPA_DB)
.to("kc.spi-map-storage-jpa-db")
.mapFrom("storage")
.paramLabel("type")
.build() .build()
}; };
} }

View file

@ -40,7 +40,7 @@ public class TransactionPropertyMappers {
if (storage != null && StorageOptions.StorageType.jpa.name().equals(storage.getValue())) { if (storage != null && StorageOptions.StorageType.jpa.name().equals(storage.getValue())) {
isJtaEnabled = true; isJtaEnabled = true;
isXaEnabled = false; isXaEnabled = true;
} }
if (!isJtaEnabled) { if (!isJtaEnabled) {

View file

@ -87,6 +87,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server. Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username> --storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user. Experimental: Sets the username of the Infinispan user.
--storage-jpa-db <type>
Experimental: The database vendor for jpa map storage. Possible values are:
postgres, cockroach. Default: postgres.
Database: Database:

View file

@ -87,6 +87,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server. Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username> --storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user. Experimental: Sets the username of the Infinispan user.
--storage-jpa-db <type>
Experimental: The database vendor for jpa map storage. Possible values are:
postgres, cockroach. Default: postgres.
Database: Database:

View file

@ -93,6 +93,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server. Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username> --storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user. Experimental: Sets the username of the Infinispan user.
--storage-jpa-db <type>
Experimental: The database vendor for jpa map storage. Possible values are:
postgres, cockroach. Default: postgres.
Database: Database:

View file

@ -93,6 +93,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server. Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username> --storage-hotrod-username <username>
Experimental: Sets the username of the Infinispan user. Experimental: Sets the username of the Infinispan user.
--storage-jpa-db <type>
Experimental: The database vendor for jpa map storage. Possible values are:
postgres, cockroach. Default: postgres.
Database: Database:

View file

@ -894,6 +894,8 @@
<keycloak.map.storage.connectionsJpa.user>${keycloak.map.storage.connectionsJpa.user}</keycloak.map.storage.connectionsJpa.user> <keycloak.map.storage.connectionsJpa.user>${keycloak.map.storage.connectionsJpa.user}</keycloak.map.storage.connectionsJpa.user>
<keycloak.map.storage.connectionsJpa.password>${keycloak.map.storage.connectionsJpa.password}</keycloak.map.storage.connectionsJpa.password> <keycloak.map.storage.connectionsJpa.password>${keycloak.map.storage.connectionsJpa.password}</keycloak.map.storage.connectionsJpa.password>
<keycloak.storage.connections.vendor>postgres</keycloak.storage.connections.vendor>
<keycloak.authorization.map.storage.provider>jpa</keycloak.authorization.map.storage.provider> <keycloak.authorization.map.storage.provider>jpa</keycloak.authorization.map.storage.provider>
<keycloak.authSession.map.storage.provider>jpa</keycloak.authSession.map.storage.provider> <keycloak.authSession.map.storage.provider>jpa</keycloak.authSession.map.storage.provider>
<keycloak.client.map.storage.provider>jpa</keycloak.client.map.storage.provider> <keycloak.client.map.storage.provider>jpa</keycloak.client.map.storage.provider>
@ -1074,6 +1076,8 @@
<keycloak.map.storage.connectionsJpa.user>${keycloak.map.storage.connectionsJpa.user}</keycloak.map.storage.connectionsJpa.user> <keycloak.map.storage.connectionsJpa.user>${keycloak.map.storage.connectionsJpa.user}</keycloak.map.storage.connectionsJpa.user>
<keycloak.map.storage.connectionsJpa.password>${keycloak.map.storage.connectionsJpa.password}</keycloak.map.storage.connectionsJpa.password> <keycloak.map.storage.connectionsJpa.password>${keycloak.map.storage.connectionsJpa.password}</keycloak.map.storage.connectionsJpa.password>
<keycloak.storage.connections.vendor>cockroach</keycloak.storage.connections.vendor>
<keycloak.authorization.map.storage.provider>jpa</keycloak.authorization.map.storage.provider> <keycloak.authorization.map.storage.provider>jpa</keycloak.authorization.map.storage.provider>
<keycloak.authSession.map.storage.provider>jpa</keycloak.authSession.map.storage.provider> <keycloak.authSession.map.storage.provider>jpa</keycloak.authSession.map.storage.provider>
<keycloak.client.map.storage.provider>jpa</keycloak.client.map.storage.provider> <keycloak.client.map.storage.provider>jpa</keycloak.client.map.storage.provider>

View file

@ -37,7 +37,7 @@ public enum StoreProvider {
@Override @Override
public void addStoreOptions(List<String> commands) { public void addStoreOptions(List<String> commands) {
commands.add("--storage=" + getAlias()); commands.add("--storage=" + getAlias());
getDbVendor().ifPresent(vendor -> commands.add("--db=" + vendor)); getDbVendor().ifPresent(vendor -> commands.add("--storage-jpa-db=" + vendor));
commands.add("--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url")); commands.add("--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"));
commands.add("--db-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user")); commands.add("--db-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user"));
commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password")); commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password"));