Additional datasources now require XA (#32403)

* Additional datasources now require XA

Closes #32402

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

* Apply suggestions from code review

Co-authored-by: Steven Hawkins <shawkins@redhat.com>
Signed-off-by: Václav Muzikář <vaclav@muzikari.cz>

* Relax validation

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

* Added a note on recovery

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

* Fix `CustomJpaEntityProviderDistTest`

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>

---------

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>
Signed-off-by: Václav Muzikář <vaclav@muzikari.cz>
Co-authored-by: Steven Hawkins <shawkins@redhat.com>
This commit is contained in:
Václav Muzikář 2024-08-29 11:16:38 +02:00 committed by GitHub
parent 2a64caf3fd
commit 7d3dcae96e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 86 additions and 6 deletions

View file

@ -190,6 +190,15 @@ Additionally, the following resources have been removed from the `common` theme:
If you previously used any of the removed resources in your theme, make sure to add them to your own theme resources instead. If you previously used any of the removed resources in your theme, make sure to add them to your own theme resources instead.
= Additional datasources now require using XA
{project_name} by default does not use XA datasources. However, this is considered unsafe if more than one datasource is used.
Starting with this release, you need to use XA datasources if you are adding additional datasources to {project_name}.
If the default datasource supports XA, you can do this by setting the `--transaction-xa-enabled=true` option. For additional datasources, you need to use
the `quarkus.datasource.<your-datasource-name>.jdbc.transactions=xa` option in your `quarkus.properties` file.
At most one datasource can be non-XA.
Recovery isn't supported when you don't have persistent storage for the transaction store.
= Hostname v1 feature removed = Hostname v1 feature removed
The deprecated hostname v1 feature was removed. This feature was deprecated in {project_name} 25 and replaced by hostname v2. If you are still using this feature, you must migrate to hostname v2. For more details, see the https://www.keycloak.org/server/hostname[Configuring the hostname (v2)] and https://www.keycloak.org/docs/latest/upgrading/#new-hostname-options[the initial migration guide]. The deprecated hostname v1 feature was removed. This feature was deprecated in {project_name} 25 and replaced by hostname v2. If you are still using this feature, you must migrate to hostname v2. For more details, see the https://www.keycloak.org/server/hostname[Configuring the hostname (v2)] and https://www.keycloak.org/docs/latest/upgrading/#new-hostname-options[the initial migration guide].

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 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.quarkus.deployment;
import io.quarkus.builder.item.EmptyBuildItem;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class CheckMultipleDatasourcesBuildStep extends EmptyBuildItem {}

View file

@ -17,13 +17,17 @@
package org.keycloak.quarkus.deployment; package org.keycloak.quarkus.deployment;
import io.quarkus.agroal.runtime.DataSourcesJdbcBuildTimeConfig;
import io.quarkus.agroal.runtime.TransactionIntegration;
import io.quarkus.agroal.runtime.health.DataSourceHealthCheck; import io.quarkus.agroal.runtime.health.DataSourceHealthCheck;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BuildTimeConditionBuildItem; import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
import io.quarkus.bootstrap.logging.InitialConfigurator; import io.quarkus.bootstrap.logging.InitialConfigurator;
import io.quarkus.builder.item.EmptyBuildItem;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem;
import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig;
import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildStep;
@ -267,13 +271,33 @@ class KeycloakProcessor {
// We do not want to initialize the JDBC driver class // We do not want to initialize the JDBC driver class
Class.forName(dbDriver.get(), false, Thread.currentThread().getContextClassLoader()); Class.forName(dbDriver.get(), false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Ignore queued TRACE and DEBUG messages for not initialized log handlers throwConfigError(String.format("Unable to find the JDBC driver (%s). You need to install it.", dbDriver.get()));
InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(new Handler[]{});
throw new ConfigurationException(String.format("Unable to find the JDBC driver (%s). You need to install it.", dbDriver.get()));
} }
} }
} }
// Inspired by AgroalProcessor
@BuildStep
@Produce(CheckMultipleDatasourcesBuildStep.class)
void checkMultipleDatasourcesUseXA(DataSourcesBuildTimeConfig dataSourcesConfig, DataSourcesJdbcBuildTimeConfig jdbcConfig) {
long nonXADatasourcesCount = dataSourcesConfig.dataSources().keySet().stream()
.map(ds -> jdbcConfig.dataSources().get(ds).jdbc())
.filter(jdbc -> jdbc.enabled() && jdbc.transactions() != TransactionIntegration.XA)
.count();
if (nonXADatasourcesCount > 1) {
throwConfigError("Multiple datasources are configured but more than 1 is using non-XA transactions. " +
"All the datasources except one must must be XA to be able to use Last Resource Commit Optimization (LRCO). " +
"Please update your configuration by setting --transaction-xa-enabled=true " +
"and/or quarkus.datasource.<your-datasource-name>.jdbc.transactions=xa.");
}
}
private void throwConfigError(String msg) {
// Ignore queued TRACE and DEBUG messages for not initialized log handlers
InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(new Handler[]{});
throw new ConfigurationException(msg);
}
/** /**
* Parse the default configuration for the User Profile provider * Parse the default configuration for the User Profile provider
*/ */
@ -344,6 +368,7 @@ class KeycloakProcessor {
@BuildStep @BuildStep
@Consume(CheckJdbcBuildStep.class) @Consume(CheckJdbcBuildStep.class)
@Consume(CheckMultipleDatasourcesBuildStep.class)
void produceDefaultPersistenceUnit(BuildProducer<PersistenceXmlDescriptorBuildItem> producer) { void produceDefaultPersistenceUnit(BuildProducer<PersistenceXmlDescriptorBuildItem> producer) {
ParsedPersistenceXmlDescriptor descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit( ParsedPersistenceXmlDescriptor descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit(
Thread.currentThread().getContextClassLoader().getResource("default-persistence.xml")); Thread.currentThread().getContextClassLoader().getResource("default-persistence.xml"));

View file

@ -17,4 +17,5 @@
quarkus.datasource.user-store.db-kind=h2 quarkus.datasource.user-store.db-kind=h2
quarkus.datasource.user-store.username=sa quarkus.datasource.user-store.username=sa
quarkus.datasource.user-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1 quarkus.datasource.user-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1
quarkus.datasource.user-store.jdbc.transactions=xa

View file

@ -17,7 +17,6 @@
package org.keycloak.it.cli.dist; package org.keycloak.it.cli.dist;
import static io.restassured.RestAssured.when;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -114,6 +113,15 @@ public class QuarkusPropertiesAutoBuildDistTest {
cliResult.assertStarted(); cliResult.assertStarted();
} }
@Test
@BeforeStartDistribution(AddNonXADatasource.class)
@Launch({ "start" })
@Order(9)
void nonXADatasourceFailsToStart(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertError("Multiple datasources are configured but more than 1 is using non-XA transactions.");
}
public static class UpdateConsoleLogLevelToWarn implements Consumer<KeycloakDistribution> { public static class UpdateConsoleLogLevelToWarn implements Consumer<KeycloakDistribution> {
@Override @Override
public void accept(KeycloakDistribution distribution) { public void accept(KeycloakDistribution distribution) {
@ -135,6 +143,7 @@ public class QuarkusPropertiesAutoBuildDistTest {
distribution.setQuarkusProperty("quarkus.datasource.user-store.db-kind", "h2"); distribution.setQuarkusProperty("quarkus.datasource.user-store.db-kind", "h2");
distribution.setQuarkusProperty("quarkus.datasource.user-store.username","sa"); distribution.setQuarkusProperty("quarkus.datasource.user-store.username","sa");
distribution.setQuarkusProperty("quarkus.datasource.user-store.jdbc.url","jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1"); distribution.setQuarkusProperty("quarkus.datasource.user-store.jdbc.url","jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1");
distribution.setQuarkusProperty("quarkus.datasource.user-store.jdbc.transactions", "xa");
} }
} }
@ -145,6 +154,17 @@ public class QuarkusPropertiesAutoBuildDistTest {
distribution.setQuarkusProperty("quarkus.datasource.user-store2.db-transactions", "enabled"); distribution.setQuarkusProperty("quarkus.datasource.user-store2.db-transactions", "enabled");
distribution.setQuarkusProperty("quarkus.datasource.user-store2.username","sa"); distribution.setQuarkusProperty("quarkus.datasource.user-store2.username","sa");
distribution.setQuarkusProperty("quarkus.datasource.user-store2.jdbc.url","jdbc:h2:mem:user-store2;DB_CLOSE_DELAY=-1"); distribution.setQuarkusProperty("quarkus.datasource.user-store2.jdbc.url","jdbc:h2:mem:user-store2;DB_CLOSE_DELAY=-1");
distribution.setQuarkusProperty("quarkus.datasource.user-store2.jdbc.transactions", "xa");
}
}
public static class AddNonXADatasource implements Consumer<KeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
distribution.setQuarkusProperty("quarkus.datasource.user-store3.db-kind", "h2");
distribution.setQuarkusProperty("quarkus.datasource.user-store3.db-transactions", "enabled");
distribution.setQuarkusProperty("quarkus.datasource.user-store3.username","sa");
distribution.setQuarkusProperty("quarkus.datasource.user-store3.jdbc.url","jdbc:h2:mem:user-store2;DB_CLOSE_DELAY=-1");
} }
} }
@ -169,4 +189,4 @@ public class QuarkusPropertiesAutoBuildDistTest {
distribution.setQuarkusProperty("quarkus.datasource.db-kind", "postgres"); distribution.setQuarkusProperty("quarkus.datasource.db-kind", "postgres");
} }
} }
} }