Skip to content

Commit

Permalink
Merge pull request #28921 from michalvavrik/feature/ot-jdbc-oracle-re…
Browse files Browse the repository at this point in the history
…gister-driver

OpenTelemetry JDBC instrumentation - fix Oracle and DB2 in native mode
  • Loading branch information
gsmet authored Jan 19, 2023
2 parents e3fccfb + 24735ab commit df60a85
Show file tree
Hide file tree
Showing 33 changed files with 1,278 additions and 21 deletions.
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));
}

@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

0 comments on commit df60a85

Please sign in to comment.