Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenTelemetry JDBC instrumentation - fix Oracle and DB2 in native mode #28921

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
{
"category": "Misc4",
"timeout": 75,
"test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, webjars-locator",
"test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, opentelemetry-jdbc-instrumentation, webjars-locator",
"os-name": "ubuntu-latest"
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,5 @@ public interface Capability {
String REDIS_CLIENT = QUARKUS_PREFIX + "redis";

String CACHE = QUARKUS_PREFIX + "cache";
String JDBC_ORACLE = QUARKUS_PREFIX + "jdbc.oracle";
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.JPMSExportBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.jdbc.db2.runtime.DB2AgroalConnectionConfigurer;
import io.quarkus.jdbc.db2.runtime.DB2ServiceBindingConverter;

public class JDBCDB2Processor {

private static final String DB2_DRIVER_CLASS = "com.ibm.db2.jcc.DB2Driver";

@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(Feature.JDBC_DB2);
Expand All @@ -30,7 +33,7 @@ FeatureBuildItem feature() {
@BuildStep
void registerDriver(BuildProducer<JdbcDriverBuildItem> jdbcDriver,
SslNativeConfigBuildItem sslNativeConfigBuildItem) {
jdbcDriver.produce(new JdbcDriverBuildItem(DatabaseKind.DB2, "com.ibm.db2.jcc.DB2Driver",
jdbcDriver.produce(new JdbcDriverBuildItem(DatabaseKind.DB2, DB2_DRIVER_CLASS,
"com.ibm.db2.jcc.DB2XADataSource"));
}

Expand All @@ -51,6 +54,15 @@ void configureAgroalConnection(BuildProducer<AdditionalBeanBuildItem> additional
}
}

@BuildStep
void registerDriverForReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
//Not strictly necessary when using Agroal, as it also registers
//any JDBC driver being configured explicitly through its configuration.
//We register it for the sake of people not using Agroal,
//for example when the driver is used with OpenTelemetry JDBC instrumentation.
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, DB2_DRIVER_CLASS));
}

michalvavrik marked this conversation as resolved.
Show resolved Hide resolved
@BuildStep
NativeImageConfigBuildItem build() {
// The DB2 JDBC driver has been updated with conditional checks for the
Expand Down
5 changes: 5 additions & 0 deletions extensions/jdbc/jdbc-oracle/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<capabilities>
<provides>io.quarkus.jdbc.oracle</provides>
</capabilities>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.jdbc.oracle.runtime.graal;

import java.security.SecureRandom;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.InjectAccessors;
import com.oracle.svm.core.annotate.TargetClass;

/**
* Substitutions required when `jdbc-oracle` is combined with `jdbc-db2`.
*/
@TargetClass(className = "java.rmi.server.ObjID")
public final class ObjIdSubstitutions {

@Alias
@InjectAccessors(SecureRandomAccessor.class)
private static SecureRandom secureRandom;

}

class SecureRandomAccessor {
private static volatile SecureRandom RANDOM;

static SecureRandom get() {
SecureRandom result = RANDOM;
if (result == null) {
/* Lazy initialization on first access. */
result = initializeOnce();
}
return result;
}

private static synchronized SecureRandom initializeOnce() {
SecureRandom result = RANDOM;
if (result != null) {
/* Double-checked locking is OK because INSTANCE is volatile. */
return result;
}

result = new SecureRandom();
RANDOM = result;
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.opentelemetry.deployment;

import java.util.List;

import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.builder.item.SimpleBuildItem;

/**
* Contains list of all {@link io.quarkus.agroal.spi.JdbcDataSourceBuildItem} using OpenTelemetryDriver.
*/
public final class OpenTelemetryDriverJdbcDataSourcesBuildItem extends SimpleBuildItem {

public final List<JdbcDataSourceBuildItem> jdbcDataSources;

OpenTelemetryDriverJdbcDataSourcesBuildItem(List<JdbcDataSourceBuildItem> jdbcDataSources) {
this.jdbcDataSources = List.copyOf(jdbcDataSources);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package io.quarkus.opentelemetry.deployment;

import static io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder.OPEN_TELEMETRY_DRIVER;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.ConfigValue;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
Expand All @@ -14,11 +19,17 @@
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.InterceptorBindingRegistrar;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
Expand Down Expand Up @@ -155,4 +166,57 @@ void createOpenTelemetry(
void storeVertxOnContextStorage(OpenTelemetryRecorder recorder, CoreVertxBuildItem vertx) {
recorder.storeVertxOnContextStorage(vertx.getVertx());
}

@BuildStep
void collectAllJdbcDataSourcesUsingOTelDriver(BuildProducer<OpenTelemetryDriverJdbcDataSourcesBuildItem> resultProducer,
List<JdbcDataSourceBuildItem> jdbcDataSources) {
final List<JdbcDataSourceBuildItem> result = new ArrayList<>();
for (JdbcDataSourceBuildItem dataSource : jdbcDataSources) {
// if the datasource is explicitly configured to use the OTel driver...
if (dataSourceUsesOTelJdbcDriver(dataSource.getName())) {
result.add(dataSource);
}
}
if (!result.isEmpty()) {
resultProducer.produce(new OpenTelemetryDriverJdbcDataSourcesBuildItem(result));
}
}

private static boolean dataSourceUsesOTelJdbcDriver(String dataSourceName) {
List<String> driverPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSourceName, "jdbc.driver");
for (String driverPropertyKey : driverPropertyKeys) {
ConfigValue explicitlyConfiguredDriverValue = ConfigProvider.getConfig().getConfigValue(driverPropertyKey);
if (explicitlyConfiguredDriverValue.getValue() != null) {
return explicitlyConfiguredDriverValue.getValue().equals(OPEN_TELEMETRY_DRIVER);
}
}
return false;
}

/**
* 'OracleDriver' register itself as driver in static initialization block, however we don't want to
* force runtime initialization for compatibility reasons, for more information please check:
* io.quarkus.jdbc.oracle.deployment.OracleMetadataOverrides#runtimeInitializeDriver
*/
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void registerOracleDriver(Optional<OpenTelemetryDriverJdbcDataSourcesBuildItem> otJdbcDataSourcesBuildItem,
List<JdbcDriverBuildItem> driverBuildItems, Capabilities capabilities, OpenTelemetryRecorder recorder) {
// check if there are data sources using OT driver and jdbc-oracle extension is present
if (otJdbcDataSourcesBuildItem.isPresent() && capabilities.isPresent(Capability.JDBC_ORACLE)) {
for (JdbcDataSourceBuildItem jdbcDataSource : otJdbcDataSourcesBuildItem.get().jdbcDataSources) {
if (jdbcDataSource.getDbKind().equals(DatabaseKind.ORACLE)) {
// now we know there is Oracle JDBC datasource
// let's find Oracle driver
for (JdbcDriverBuildItem driverBuildItem : driverBuildItems) {
if (DatabaseKind.ORACLE.equals(driverBuildItem.getDbKind())) {
recorder.registerJdbcDriver(driverBuildItem.getDriverClass());
break;
}
}
break;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.ConfigValue;
import java.util.Optional;

import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
Expand All @@ -15,17 +13,18 @@
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.builditem.DevServicesAdditionalConfigBuildItem;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
import io.quarkus.opentelemetry.deployment.OpenTelemetryDriverJdbcDataSourcesBuildItem;
import io.quarkus.opentelemetry.deployment.OpenTelemetryEnabled;

@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { OpenTelemetryEnabled.class, GlobalDevServicesConfig.Enabled.class })
public class DevServicesOpenTelemetryProcessor {

@BuildStep
void devServicesDatasources(List<JdbcDataSourceBuildItem> jdbcDataSources,
void devServicesDatasources(Optional<OpenTelemetryDriverJdbcDataSourcesBuildItem> otJdbcDataSourcesBuildItem,
BuildProducer<DevServicesAdditionalConfigBuildItem> devServicesAdditionalConfig) {
for (JdbcDataSourceBuildItem dataSource : jdbcDataSources) {
// if the datasource is explicitly configured to use the OTel driver...
if (dataSourceUsesOTelJdbcDriver(dataSource.getName())) {
if (otJdbcDataSourcesBuildItem.isPresent()) {
// found datasources explicitly configured to use the OTel driver
for (JdbcDataSourceBuildItem dataSource : otJdbcDataSourcesBuildItem.get().jdbcDataSources) {
List<String> urlPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSource.getName(), "jdbc.url");
devServicesAdditionalConfig.produce(new DevServicesAdditionalConfigBuildItem(devServicesConfig -> {
Map<String, String> overrides = new HashMap<>();
Expand All @@ -42,16 +41,4 @@ void devServicesDatasources(List<JdbcDataSourceBuildItem> jdbcDataSources,
}
}
}

private boolean dataSourceUsesOTelJdbcDriver(String dataSourceName) {
List<String> driverPropertyKeys = DataSourceUtil.dataSourcePropertyKeys(dataSourceName, "jdbc.driver");
for (String driverPropertyKey : driverPropertyKeys) {
ConfigValue explicitlyConfiguredDriverValue = ConfigProvider.getConfig().getConfigValue(driverPropertyKey);
if (explicitlyConfiguredDriverValue.getValue() != null) {
return explicitlyConfiguredDriverValue.getValue()
.equals("io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver");
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.quarkus.opentelemetry.runtime;

import java.lang.reflect.InvocationTargetException;
import java.sql.Driver;
import java.util.function.Supplier;

import org.jboss.logging.Logger;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.ContextStorage;
Expand All @@ -16,6 +20,9 @@
@Recorder
public class OpenTelemetryRecorder {

public static final String OPEN_TELEMETRY_DRIVER = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver";
private static final Logger LOG = Logger.getLogger(OpenTelemetryRecorder.class);

/* STATIC INIT */
public void resetGlobalOpenTelemetryForDevMode() {
GlobalOpenTelemetry.resetForTest();
Expand Down Expand Up @@ -46,4 +53,31 @@ public void eagerlyCreateContextStorage() {
public void storeVertxOnContextStorage(Supplier<Vertx> vertx) {
QuarkusContextStorage.vertx = vertx.get();
}

public void registerJdbcDriver(String driverClass) {
try {
var constructors = Class
.forName(driverClass, true, Thread.currentThread().getContextClassLoader())
.getConstructors();
if (constructors.length == 1) {
// create driver
Driver driver = ((Driver) constructors[0].newInstance());
// register the driver with OpenTelemetryDriver
Class
.forName(OPEN_TELEMETRY_DRIVER, true, Thread.currentThread().getContextClassLoader())
.getMethod("addDriverCandidate", Driver.class)
.invoke(null, driver);
} else {
// drivers should have default constructor
LOG.warn(String.format(
"Class '%s' has more than one constructor and won't be registered as driver. JDBC instrumentation might not work properly in native mode.",
driverClass));
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException
| ClassNotFoundException e) {
LOG.warn(String.format(
"Failed to register '%s' driver. JDBC instrumentation might not work properly in native mode.",
driverClass));
}
}
}
36 changes: 36 additions & 0 deletions integration-tests/opentelemetry-jdbc-instrumentation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# OpenTelemetry JDBC instrumentation example

## Running the tests


To run the tests in a standard JVM with an Oracle, PostgreSQL and MariaDB databases started as a Docker containers, you can run the following command:

```
mvn verify -Dtest-containers -Dstart-containers
```

To also test as a native image, add `-Dnative`:

```
mvn verify -Dtest-containers -Dstart-containers -Dnative
```

You can also run tests with a specific database image, just set the following parameters:

- `oracle.image` for Oracle
- `postgres.image` for PostgreSQL
- `mariadb.image` for MariaDB
- `db2.image` for Db2

For example to run tests with the latest PostgreSQL database image, you can run the following command:

```
mvn verify -Dtest-containers -Dstart-containers -Dpostgres.image=docker.io/postgres:latest
```

Unfortunately booting DB2 is slow and needs to set a generous timeout, therefore the DB2 test is disabled by default.
You can enable it with `enable-db2` system property like this:

```
mvn verify -Dtest-containers -Dstart-containers -Denable-db2
```
Loading