[feat][client] Embed GraalVM native image config#25883
Conversation
lhotari
left a comment
There was a problem hiding this comment.
The changes look good so far. However, we'd need an integration test to verify that a Pulsar client application can be compiled to a native image. Besides this, I'd assume that there would have to be a smoke test to cover at least basic functionality (produce/consume).
Would it be possible to add the integration test to tests/pulsar-client-native-image directory? It would be great if the test could be written in TestNG and call the native application with java.lang.ProcessBuilder or similar. The native application could have different cli options to perform the actual logic of each test case. I guess there could also be other ways to add test coverage. Perhaps the TestNG class could be directly native compiled without the need for a separate native application, at least junit is supported in the Gradle GraalVM plugin, however TestNG support is most likely missing. We could add Junit Jupiter dependencies to Pulsar alongside TestNG if that makes things easier. There has been plans to start migrating to Junit Jupiter in Pulsar anyways.
GraalVM Gradle plugin docs: https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html
…nfig Address review feedback on PR apache#25883 (native-image support): - Add tests/pulsar-client-native-image integration module: a TestNG smoke test (NativeImageSmokeTest) that drives the natively-compiled NativeImageTesterApp via java.lang.ProcessBuilder, covering native compilation plus basic produce/consume against a Testcontainers Pulsar broker. Registered in tests/pom.xml under the nativeImageTests profile. - Add native-image metadata for pulsar-client-admin-original (reflect-config.json, resource-config.json, native-image.properties) covering the Jackson-serialized admin model DTOs and the runtime-initialized AsyncHttpConnector, with a NativeImageConfigAdminTest static validator. - Add ASF license header to the pulsar-client native-image.properties (pre-existing RAT violation from the prior commit). - Add ci-pulsar-native-image.yaml GitHub Actions workflow (nightly + workflow_dispatch) building with GraalVM community JDK 17. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review, @lhotari! I've added the integration test coverage you asked for. It follows the approach you suggested exactly. New module:
I went with the separate-native-application + ProcessBuilder design rather than native-compiling the TestNG class directly, since (as you noted) TestNG native support is missing today. Happy to revisit that toward JUnit Jupiter if/when the migration lands. I also added native-image metadata for Let me know if you'd like any changes to the test structure or CLI surface. |
Add META-INF/native-image configuration files (reflect-config.json, resource-config.json, native-image.properties) so that GraalVM's native-image tool can build applications using the Pulsar client without framework-specific configuration. Includes a test (NativeImageConfigTest) that validates all referenced classes exist on the classpath and all Authentication implementations are registered, so the config does not drift silently as the codebase evolves. This eliminates the need for downstream frameworks (Quarkus, Micronaut, Spring Native) to independently maintain reflection and class initialization metadata for Pulsar client internals.
…nfig Address review feedback on PR apache#25883 (native-image support). Reworked for the Gradle build (master migrated from Maven to Gradle): - Add tests/pulsar-client-native-image integration module with a Gradle build.gradle.kts using the GraalVM native-build-tools plugin (org.graalvm.buildtools.native). A TestNG smoke test (NativeImageSmokeTest) drives the natively-compiled NativeImageTesterApp via java.lang.ProcessBuilder, covering native compilation plus basic produce/consume against a Testcontainers Pulsar broker. Quick build mode (quickBuild = true) keeps CI compilation within the integration test budget. Registered in settings.gradle.kts. - Add native-image metadata for pulsar-client-admin-original (reflect-config.json, resource-config.json, native-image.properties) covering the Jackson-serialized admin model DTOs and the runtime-initialized AsyncHttpConnector, with a NativeImageConfigAdminTest static validator. - Add ASF license header to the pulsar-client native-image.properties (pre-existing RAT violation from the prior commit). - Wire native-image tests into the existing Pulsar CI integration test matrix (pulsar-ci.yaml NATIVE_IMAGE group + a test_group_native_image entry in run_integration_group_gradle.sh) instead of a standalone workflow, with a conditional GraalVM JDK setup step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2efd53b to
071ea20
Compare
Co-authored-by: Lari Hotari <lhotari@users.noreply.github.com>
Remove blank line between static and regular imports to comply with the ImportOrder checkstyle rule. Assisted-by: Claude Code
Resolve this error: The configured GraalVM reachability metadata repository at /home/runner/.gradle/native-build-tools/repositories/c8a0af0125b1a60c42de4ada4a5284b8cc6826d3/exploded provides a reachability-metadata schema, but your GraalVM installation at /opt/hostedtoolcache/graalvm-community-jdk-21.0.2_linux-x64_bin/21.0.2/x64/graalvm-community-openjdk-21.0.2+13.1 does not. Please update your GraalVM installation to a newer version. Update to the latest available release in your line. For example: Update your GraalVM version from 25.0.0 to the latest 25 release. GraalVM gradle plugin 1.1.1 requires GraalVM 25
The CI - Unit - Other group runs a broad `./gradlew ... test` across all modules on the Corretto JDK. The :tests:pulsar-client-native-image:test task depends on :nativeCompile (GraalVM native-build-tools), so it was swept into the unit run and failed with "native-image wasn't found ... JDK isn't a GraalVM distribution" on the non-GraalVM runner. Exclude :tests:pulsar-client-native-image:test from the OTHER group, the same way the shaded client test modules are already excluded. The native image build/test still runs in the dedicated NATIVE_IMAGE integration group, which sets up a GraalVM JDK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Main Issue: quarkusio/quarkus#48776
Motivation
Applications using
pulsar-client-originalin GraalVM native-image builds currently fail unless the consuming framework (Quarkus, Micronaut, Spring Native, etc.) provides its own reflection and class initialization metadata for Pulsar client internals. This forces every framework to independently reverse-engineer and maintain configuration that tracks Pulsar's internal class layout — and when it drifts, native builds break silently.A concrete example: Quarkus's Pulsar extension registered
org.apache.pulsar.client.util.WithSNISslEngineFactoryfor runtime initialization, but that class was removed in Pulsar 4.x. Because the configuration lived downstream instead of in the client itself, the breakage went unnoticed for a year (quarkusio/quarkus#48776).Embedding the configuration directly in
pulsar-client-originalfollowingthe GraalVM embedded configuration convention means the
native-imagetool picks it up automatically from the classpath, keeping the config in sync with the code that owns the classes.Modifications
Added three GraalVM native-image configuration files under
pulsar-client/src/main/resources/META-INF/native-image/org.apache.pulsar/pulsar-client-original/:reflect-config.json — Registers 20 classes for reflective access:
the three
ConfigurationDataclasses (deserialized by Jackson), all sixAuthenticationimplementations (instantiated by name viaAuthenticationUtil.create()), the OAuth2 protocol model classes, schemainternals (
ProtoBufParsingInfo,ProtobufNativeSchemaData,KeyValue),DataURLStreamHandler, andSecretsSerializer.native-image.properties — Marks eight classes for runtime
initialization that would otherwise fail during native-image build due to
eager static initialization of Netty allocators, thread pools, or HTTP
clients:
PulsarByteBufAllocator,Commands,Backoff,TokenClient,GenericProtobufNativeSchema,ConnectionPool,ControlledClusterFailover, andHttpClient.resource-config.json — Includes the async-http-client default
properties files that are loaded via classloader at runtime.
Added
NativeImageConfigTestthat validates:reflect-config.jsonexist on the classpathnative-image.propertiesexist on the classpathAuthenticationimplementations shipped with the client are registeredThis test catches configuration drift early — if a class is renamed, removed, or a new
Authenticationplugin is added without updating the config, the test fails with a descriptive message.Note: the shaded
pulsar-clientJAR intentionally stripsMETA-INF/native-image/**in its shade plugin filters, which is correct since shading relocates packages and invalidates the original class names. Users building native images should depend onpulsar-client-original.Verifying this change
This change added tests and can be verified as follows:
NativeImageConfigTestthat validates all 20 reflection entries and8 runtime-initialized classes resolve on the classpath, and that all
Authenticationimplementations are registered.source tree.
SmallRyeReactiveMessagingPulsarProcessor) was validated by successfullycompiling six native-image integration tests in the Quarkus
integration-tests/reactive-messaging-pulsarmodule against Pulsar 4.2.1.Does this pull request potentially affect one of the following parts:
Documentation
doc-not-needed