-
Notifications
You must be signed in to change notification settings - Fork 239
gRPC support for opensearch-java #2062
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
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
8a5f19b
feat: add transparent gRPC transport as separate java-client-grpc module
dayana-j 51cd03e
ci: retrigger CI
dayana-j a7bbd43
Making the FieldMappingUtil package private
dayana-j 874c174
Making this package private
dayana-j b7575b4
Making BasicAuthInterceptor package private
dayana-j a667a09
Remove unused toHttpStatus method and fix test package for package-pr…
dayana-j b8d8720
refactor: split AWS SigV4 into AwsGrpcTransport per maintainer feedback
dayana-j bd70c72
refactor: remove silent REST fallback from HybridTransport
dayana-j b9811ba
docs: update comments to reflect routing instead of fallback
dayana-j 94f2d6c
fix: make integration test base classes self-contained
dayana-j 9aae7f1
fix: lower java-client-grpc baseline from JDK 11 to JDK 8
dayana-j 178f0bc
fix: remove explicit jackson test deps (transitive via java-client)
dayana-j e6bf191
fix: wire up integration tests with java21 source set
dayana-j 69a7311
ci: add OpenSearch 3.5.0 to integration test matrix
dayana-j 4b98fc8
fix: replace wildcard import with explicit imports (spotless)
dayana-j 9659742
fix: remove java21 classes from unitTest task
dayana-j 32aeab0
fix: spotless formatting for samples (license header, import order)
dayana-j 4480aa3
fix: skip gRPC integration tests when port is unreachable
dayana-j 70df55b
fix: only enable gRPC in testcontainer for OpenSearch 3.5.0+
dayana-j ae935be
ci: exclude grpc integration tests until OpenSearch 3.5.0 is released
dayana-j 32f38c9
fix: graceful skip on container failure + re-enable 3.5.0 in CI
dayana-j b6b098f
fix: spotless formatting on GrpcTestContainerRule
dayana-j 8f5a6ae
fix: remove grpcStatusToHttpStatus from FieldMappingUtil
dayana-j 2d2b0c6
fix: skip gRPC tests early with assumeTrue for unsupported versions
dayana-j 87ba2ff
fix: make GrpcChannelFactory methods package-private
dayana-j cf6e8ce
fix: apply maintainer suggestions on visibility and naming
dayana-j f476ce2
Merge branch 'opensearch-project:main' into grpc-java
dayana-j File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,4 +14,5 @@ services: | |
| - bootstrap.memory_lock=true | ||
| ports: | ||
| - "9200:9200" | ||
| - "9400:9400" | ||
| user: opensearch | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,6 @@ | ||
| cluster.name: "docker-cluster" | ||
| network.host: 0.0.0.0 | ||
|
|
||
| # gRPC transport configuration | ||
| aux.transport.types: [transport-grpc] | ||
| aux.transport.transport-grpc.port: '9400' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| /* | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * | ||
| * The OpenSearch Contributors require contributions made to | ||
| * this file be licensed under the Apache-2.0 license or a | ||
| * compatible open source license. | ||
| */ | ||
|
|
||
| plugins { | ||
| java | ||
| `java-library` | ||
| `maven-publish` | ||
| id("opensearch-java.spotless-conventions") | ||
| } | ||
|
|
||
| repositories { | ||
| mavenLocal() | ||
| maven(url = "https://ci.opensearch.org/ci/dbc/snapshots/maven/") | ||
| mavenCentral() | ||
| } | ||
|
|
||
| java { | ||
| targetCompatibility = JavaVersion.VERSION_1_8 | ||
| sourceCompatibility = JavaVersion.VERSION_1_8 | ||
|
|
||
| withJavadocJar() | ||
| withSourcesJar() | ||
| } | ||
|
|
||
| val opensearchVersion = "3.5.0-SNAPSHOT" | ||
| val grpcVersion = "1.68.0" | ||
| val protobufVersion = "3.25.5" | ||
| val opensearchProtobufVersion = "1.2.0" | ||
|
|
||
| dependencies { | ||
| // Depend on java-client core | ||
| api(project(":java-client")) | ||
|
|
||
| // gRPC runtime | ||
| api("io.grpc", "grpc-api", grpcVersion) | ||
| api("io.grpc", "grpc-stub", grpcVersion) | ||
| api("io.grpc", "grpc-protobuf", grpcVersion) | ||
| api("io.grpc", "grpc-netty-shaded", grpcVersion) | ||
| api("com.google.protobuf", "protobuf-java", protobufVersion) | ||
|
|
||
| // OpenSearch Protobufs (compiled protobuf Java classes) | ||
| api("org.opensearch", "protobufs", opensearchProtobufVersion) | ||
|
|
||
| // For AwsSdk2 SigV4 support (optional, compile-only) | ||
| compileOnly("software.amazon.awssdk", "sdk-core", "[2.21,3.0)") | ||
| compileOnly("software.amazon.awssdk", "auth", "[2.21,3.0)") | ||
| compileOnly("software.amazon.awssdk", "http-auth-aws", "[2.21,3.0)") | ||
|
|
||
| // Test dependencies | ||
| testImplementation("io.grpc", "grpc-testing", grpcVersion) | ||
| testImplementation("junit", "junit", "4.13.2") | ||
| testImplementation("org.opensearch.client", "opensearch-rest-client", opensearchVersion) | ||
| testImplementation("software.amazon.awssdk", "sdk-core", "[2.21,3.0)") | ||
| testImplementation("software.amazon.awssdk", "auth", "[2.21,3.0)") | ||
| testImplementation("software.amazon.awssdk", "http-auth-aws", "[2.21,3.0)") | ||
| } | ||
|
|
||
| tasks.test { | ||
| systemProperty("tests.security.manager", "false") | ||
| } | ||
|
|
||
| val unitTest = tasks.register<Test>("unitTest") { | ||
| filter { | ||
| excludeTestsMatching("org.opensearch.client.opensearch.integTest.*") | ||
| } | ||
| systemProperty("tests.security.manager", "false") | ||
| } | ||
|
|
||
| val integrationTest = tasks.register<Test>("integrationTest") { | ||
| filter { | ||
| includeTestsMatching("org.opensearch.client.opensearch.integTest.*") | ||
| } | ||
| systemProperty("tests.security.manager", "false") | ||
| systemProperty("https", System.getProperty("https", "false")) | ||
| systemProperty("user", System.getProperty("user", "admin")) | ||
| systemProperty("password", System.getProperty("password", "admin")) | ||
| systemProperty("tests.opensearch.testcontainers.enabled", | ||
| System.getProperty("tests.opensearch.testcontainers.enabled", "true")) | ||
| systemProperty("tests.opensearch.version", | ||
| System.getProperty("tests.opensearch.version", opensearchVersion)) | ||
| } | ||
|
|
||
| // Integration tests require Java 21+ and live in src/test/java11 | ||
| val runtimeJavaVersion = (System.getProperty("runtime.java")?.toInt())?.let(JavaVersion::toVersion) ?: JavaVersion.current() | ||
| if (runtimeJavaVersion >= JavaVersion.VERSION_21) { | ||
| val java21: SourceSet = sourceSets.create("java21") { | ||
| java { | ||
| compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output | ||
| runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output | ||
| srcDir("src/test/java11") | ||
| } | ||
| } | ||
|
|
||
| configurations[java21.implementationConfigurationName].extendsFrom(configurations.testImplementation.get()) | ||
| configurations[java21.runtimeOnlyConfigurationName].extendsFrom(configurations.testRuntimeOnly.get()) | ||
|
|
||
| dependencies { | ||
| "java21Implementation"("org.opensearch.test", "framework", opensearchVersion) { | ||
| exclude(group = "org.hamcrest") | ||
| } | ||
| "java21Implementation"("org.opensearch:opensearch-testcontainers:4.1.0") | ||
| "java21Implementation"("org.testcontainers:testcontainers:2.0.5") | ||
| } | ||
|
|
||
| tasks.named<JavaCompile>("compileJava21Java") { | ||
| targetCompatibility = JavaVersion.VERSION_21.toString() | ||
| sourceCompatibility = JavaVersion.VERSION_21.toString() | ||
| } | ||
|
|
||
| tasks.named<JavaCompile>("compileTestJava") { | ||
| targetCompatibility = JavaVersion.VERSION_21.toString() | ||
| sourceCompatibility = JavaVersion.VERSION_21.toString() | ||
| } | ||
|
|
||
| integrationTest.configure { | ||
| testClassesDirs += java21.output.classesDirs | ||
| classpath = sourceSets["java21"].runtimeClasspath | ||
| } | ||
| } | ||
|
|
||
| tasks.withType<Jar> { | ||
| manifest { | ||
| attributes["Implementation-Title"] = "OpenSearch Java Client - gRPC Transport" | ||
| attributes["Implementation-Vendor"] = "OpenSearch" | ||
| attributes["Implementation-URL"] = "https://github.com/opensearch-project/opensearch-java/" | ||
| } | ||
|
|
||
| metaInf { | ||
| from("../LICENSE.txt") | ||
| from("../NOTICE.txt") | ||
| } | ||
| } | ||
|
|
||
| publishing { | ||
| publications { | ||
| create<MavenPublication>("publishMaven") { | ||
| from(components["java"]) | ||
| pom { | ||
| name.set("OpenSearch Java Client - gRPC Transport") | ||
| packaging = "jar" | ||
| artifactId = "opensearch-java-grpc" | ||
| description.set("gRPC transport for the OpenSearch Java Client.") | ||
| url.set("https://github.com/opensearch-project/opensearch-java/") | ||
| } | ||
| } | ||
| } | ||
| } | ||
156 changes: 156 additions & 0 deletions
156
java-client-grpc/src/main/java/org/opensearch/client/transport/grpc/AwsGrpcTransport.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| /* | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * | ||
| * The OpenSearch Contributors require contributions made to | ||
| * this file be licensed under the Apache-2.0 license or a | ||
| * compatible open source license. | ||
| */ | ||
|
|
||
| package org.opensearch.client.transport.grpc; | ||
|
|
||
| import io.grpc.ManagedChannel; | ||
| import javax.annotation.Nullable; | ||
| import org.opensearch.client.json.JsonpMapper; | ||
| import org.opensearch.client.transport.TransportOptions; | ||
|
|
||
| /** | ||
| * AWS-specific gRPC transport that adds SigV4 signing to the gRPC channel. | ||
| * <p> | ||
| * This follows the same pattern as {@code AwsSdk2Transport} vs | ||
| * {@code ApacheHttpClient5Transport} — separating general transport from | ||
| * AWS-specific authentication. | ||
| * <p> | ||
| * Usage: | ||
| * <pre>{@code | ||
| * var grpcTransport = AwsGrpcTransport.builder("domain.us-east-1.es.amazonaws.com", 9400) | ||
| * .jsonpMapper(new JacksonJsonpMapper()) | ||
| * .tls(GrpcTlsConfig.builder().build()) | ||
| * .sigV4(GrpcSigV4Config.builder() | ||
| * .region(Region.US_EAST_1) | ||
| * .service("es") | ||
| * .credentialsProvider(DefaultCredentialsProvider.create()) | ||
| * .build()) | ||
| * .build(); | ||
| * }</pre> | ||
| */ | ||
| public class AwsGrpcTransport extends GrpcTransport { | ||
|
|
||
| private final GrpcSigV4Interceptor sigV4Interceptor; | ||
|
|
||
| AwsGrpcTransport( | ||
| ManagedChannel channel, | ||
| JsonpMapper jsonpMapper, | ||
| GrpcTransportOptions grpcOptions, | ||
| @Nullable TransportOptions transportOptions, | ||
| GrpcSigV4Interceptor sigV4Interceptor | ||
| ) { | ||
| super(channel, jsonpMapper, grpcOptions, transportOptions); | ||
| this.sigV4Interceptor = sigV4Interceptor; | ||
| } | ||
|
|
||
| @Override | ||
| protected void preProcessBulk(org.opensearch.protobufs.BulkRequest protoRequest) { | ||
| if (sigV4Interceptor != null) { | ||
| String payloadHash = GrpcSigV4Interceptor.computePayloadHash(protoRequest.toByteArray()); | ||
| sigV4Interceptor.setPayloadHash(payloadHash); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a builder for AwsGrpcTransport. | ||
| * | ||
| * @param host the gRPC server hostname | ||
| * @param port the gRPC server port (default: 9400) | ||
| */ | ||
| public static Builder awsBuilder(String host, int port) { | ||
| return new Builder(host, port); | ||
| } | ||
|
|
||
| public static final class Builder { | ||
| private final String host; | ||
| private final int port; | ||
| private JsonpMapper jsonpMapper; | ||
| private GrpcTransportOptions grpcOptions = GrpcTransportOptions.defaults(); | ||
| private TransportOptions transportOptions; | ||
| private GrpcTlsConfig tlsConfig; | ||
| private GrpcSigV4Config sigV4Config; | ||
| private ManagedChannel channel; | ||
|
|
||
| Builder(String host, int port) { | ||
| this.host = host; | ||
| this.port = port; | ||
| } | ||
|
|
||
| public Builder jsonpMapper(JsonpMapper mapper) { | ||
| this.jsonpMapper = mapper; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder grpcOptions(GrpcTransportOptions options) { | ||
| this.grpcOptions = options; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder transportOptions(TransportOptions options) { | ||
| this.transportOptions = options; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Configures TLS for the gRPC channel. Required for SigV4. | ||
| */ | ||
| public Builder tls(GrpcTlsConfig tlsConfig) { | ||
| this.tlsConfig = tlsConfig; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Configures AWS SigV4 signing for the gRPC channel. | ||
| * TLS is required when using SigV4. | ||
| * | ||
| * @param sigV4Config the SigV4 configuration (region, service, credentials) | ||
| */ | ||
| public Builder sigV4(GrpcSigV4Config sigV4Config) { | ||
| this.sigV4Config = sigV4Config; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Inject a pre-built channel (primarily for testing). | ||
| */ | ||
| public Builder channel(ManagedChannel channel) { | ||
| this.channel = channel; | ||
| return this; | ||
| } | ||
|
|
||
| public AwsGrpcTransport build() { | ||
| if (jsonpMapper == null) { | ||
| throw new IllegalArgumentException("jsonpMapper is required"); | ||
| } | ||
| if (sigV4Config == null) { | ||
| throw new IllegalArgumentException("sigV4 config is required for AwsGrpcTransport. Use GrpcTransport for non-AWS usage."); | ||
| } | ||
| if (tlsConfig == null) { | ||
| throw new IllegalStateException("TLS is required when using SigV4 signing. Configure TLS with .tls() before .sigV4()."); | ||
| } | ||
|
|
||
| ManagedChannel ch = this.channel; | ||
| GrpcSigV4Interceptor sigV4InterceptorRef = null; | ||
|
|
||
| if (ch == null) { | ||
| java.util.List<io.grpc.ClientInterceptor> interceptors = new java.util.ArrayList<>(); | ||
|
|
||
| sigV4InterceptorRef = new GrpcSigV4Interceptor(sigV4Config, host); | ||
| interceptors.add(sigV4InterceptorRef); | ||
|
|
||
| try { | ||
| ch = GrpcChannelFactory.createChannel(host, port, tlsConfig, grpcOptions, interceptors); | ||
| } catch (java.io.IOException e) { | ||
| throw new IllegalStateException("Failed to create gRPC channel: " + e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| return new AwsGrpcTransport(ch, jsonpMapper, grpcOptions, transportOptions, sigV4InterceptorRef); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.