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)
.description("The database vendor.")
.defaultValue("dev-file")
.expectedValues(Database::getAliases)
.expectedValues(Database::getLegacyStoreAliases)
.buildTime(true)
.build();

View file

@ -27,6 +27,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.config.database.Database;
public class StorageOptions {
@ -333,6 +334,14 @@ public class StorageOptions {
.description("Root directory for file map store.")
.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) {
return "Sets a storage mechanism for " + areaAsText + ".";
}
@ -344,4 +353,11 @@ public class StorageOptions {
private static List<String> getExpectedCacheNames() {
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.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
@ -94,12 +96,31 @@ public final class Database {
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 {
H2("h2",
Enabled.LEGACY_ONLY,
"org.h2.jdbcx.JdbcDataSource",
"org.h2.Driver",
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
@ -146,6 +167,7 @@ public final class Database {
"dev-mem", "dev-file"
),
MYSQL("mysql",
Enabled.LEGACY_ONLY,
"com.mysql.cj.jdbc.MysqlXADataSource",
"com.mysql.cj.jdbc.Driver",
"org.hibernate.dialect.MySQL8Dialect",
@ -153,6 +175,7 @@ public final class Database {
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase")
),
MARIADB("mariadb",
Enabled.LEGACY_ONLY,
"org.mariadb.jdbc.MariaDbDataSource",
"org.mariadb.jdbc.Driver",
"org.hibernate.dialect.MariaDBDialect",
@ -160,15 +183,25 @@ public final class Database {
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase")
),
POSTGRES("postgresql",
Enabled.ENABLED,
"org.postgresql.xa.PGXADataSource",
"org.postgresql.Driver",
"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:}",
asList("liquibase.database.core.PostgresDatabase", "liquibase.database.core.CockroachDatabase",
"org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase"),
asList("liquibase.database.core.PostgresDatabase", "org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase"),
"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",
Enabled.LEGACY_ONLY,
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource",
"com.microsoft.sqlserver.jdbc.SQLServerDriver",
"org.hibernate.dialect.SQLServer2016Dialect",
@ -177,6 +210,7 @@ public final class Database {
"mssql"
),
ORACLE("oracle",
Enabled.LEGACY_ONLY,
"oracle.jdbc.xa.client.OracleXADataSource",
"oracle.jdbc.driver.OracleDriver",
"org.hibernate.dialect.Oracle12cDialect",
@ -185,6 +219,7 @@ public final class Database {
);
final String databaseKind;
final Enabled enabled;
final String xaDriver;
final String nonXaDriver;
final Function<String, String> dialect;
@ -192,20 +227,21 @@ public final class Database {
final List<String> liquibaseTypes;
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) {
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) {
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,
String... aliases) {
this.databaseKind = databaseKind;
this.enabled = enabled;
this.xaDriver = xaDriver;
this.nonXaDriver = nonXaDriver;
this.dialect = dialect;
@ -214,6 +250,14 @@ public final class Database {
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) {
return databaseKind.equals(dbKind);
}
@ -223,4 +267,18 @@ public final class Database {
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 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.configuration.Configuration.getRawValue;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
@ -99,7 +100,7 @@ final class DatabasePropertyMappers {
if (url.isPresent()) {
if (isJpaStore()) {
return Database.getDefaultUrl(Database.Vendor.POSTGRES.name().toLowerCase());
return Database.getDefaultUrl(getJpaStoreDbVendor().name().toLowerCase());
}
return url;
}
@ -109,7 +110,8 @@ final class DatabasePropertyMappers {
private static Optional<String> getXaOrNonXaDriver(Optional<String> value, ConfigSourceInterceptorContext context) {
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");
@ -133,7 +135,7 @@ final class DatabasePropertyMappers {
private static Optional<String> toDatabaseKind(Optional<String> db, ConfigSourceInterceptorContext context) {
if (isJpaStore()) {
return Database.getDatabaseKind(Database.Vendor.POSTGRES.name().toLowerCase());
return Database.getDatabaseKind(getJpaStoreDbVendor().name().toLowerCase());
}
Optional<String> databaseKind = Database.getDatabaseKind(db.get());
@ -142,14 +144,14 @@ final class DatabasePropertyMappers {
return databaseKind;
}
addInitializationException(invalidDatabaseVendor(db.get(), Database.getAliases()));
addInitializationException(invalidDatabaseVendor(db.get(), Database.getLegacyStoreAliases()));
return of("h2");
}
private static Optional<String> resolveDatabaseVendor(Optional<String> db, ConfigSourceInterceptorContext context) {
if (isJpaStore()) {
return of(Database.Vendor.POSTGRES.name().toLowerCase());
return Optional.of(getJpaStoreDbVendor().name().toLowerCase());
}
if (db.isEmpty()) {
@ -204,16 +206,13 @@ final class DatabasePropertyMappers {
return Database.getDialect("dev-file");
}
private static String getDefaultVendor() {
if (isJpaStore()) {
return Database.Vendor.POSTGRES.name().toLowerCase();
}
return "dev-file";
}
private static boolean isJpaStore() {
String storage = getRawValue(NS_KEYCLOAK_PREFIX.concat(STORAGE.getKey()));
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")
.mapFrom("storage")
.paramLabel("dir")
.build(),
fromOption(StorageOptions.STORAGE_JPA_DB)
.to("kc.spi-map-storage-jpa-db")
.mapFrom("storage")
.paramLabel("type")
.build()
};
}

View file

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

View file

@ -87,6 +87,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
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:

View file

@ -87,6 +87,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
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:

View file

@ -93,6 +93,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
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:

View file

@ -93,6 +93,9 @@ Storage (Experimental):
Experimental: Sets the port of the Infinispan server.
--storage-hotrod-username <username>
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:

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.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.authSession.map.storage.provider>jpa</keycloak.authSession.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.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.authSession.map.storage.provider>jpa</keycloak.authSession.map.storage.provider>
<keycloak.client.map.storage.provider>jpa</keycloak.client.map.storage.provider>

View file

@ -37,7 +37,7 @@ public enum StoreProvider {
@Override
public void addStoreOptions(List<String> commands) {
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-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user"));
commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password"));