diff --git a/.github/workflows/ci-actions.yml b/.github/workflows/ci-actions.yml index e6ca95ee6b14f..b4fac42d7d91d 100644 --- a/.github/workflows/ci-actions.yml +++ b/.github/workflows/ci-actions.yml @@ -575,6 +575,7 @@ jobs: grpc-interceptors grpc-mutual-auth grpc-plain-text + grpc-plain-text-mutiny grpc-proto-v2 grpc-streaming grpc-tls diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java index af6228a251384..d3b0d5a3bfc8d 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java @@ -160,7 +160,13 @@ HealthBuildItem addHealthChecks(GrpcServerBuildTimeConfig config, NativeImageConfigBuildItem nativeImageConfiguration() { NativeImageConfigBuildItem.Builder builder = NativeImageConfigBuildItem.builder() .addRuntimeInitializedClass("io.grpc.netty.Utils$ByteBufAllocatorPreferDirectHolder") - .addRuntimeInitializedClass("io.grpc.netty.Utils$ByteBufAllocatorPreferHeapHolder"); + .addRuntimeInitializedClass("io.grpc.netty.Utils$ByteBufAllocatorPreferHeapHolder") + // substitutions are runtime-only, Utils have to be substituted until we cannot use EPoll + // in native. NettyServerBuilder and NettyChannelBuilder would "bring in" Utils in build time + // if they were not marked as runtime initialized: + .addRuntimeInitializedClass("io.grpc.netty.Utils") + .addRuntimeInitializedClass("io.grpc.netty.NettyServerBuilder") + .addRuntimeInitializedClass("io.grpc.netty.NettyChannelBuilder"); return builder.build(); } diff --git a/extensions/grpc/runtime/pom.xml b/extensions/grpc/runtime/pom.xml index 4695dc08aa157..96924164b9dc6 100644 --- a/extensions/grpc/runtime/pom.xml +++ b/extensions/grpc/runtime/pom.xml @@ -17,6 +17,10 @@ jakarta.annotation jakarta.annotation-api + + com.google.code.findbugs + jsr305 + io.vertx vertx-grpc diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubtitutions.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubstitutions.java similarity index 87% rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubtitutions.java rename to extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubstitutions.java index 9b90c24e54e40..4337cbbb391b1 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubtitutions.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubstitutions.java @@ -55,6 +55,20 @@ public static SslContextBuilder configure(SslContextBuilder builder, Provider jd } +@TargetClass(className = "io.grpc.netty.Utils") +final class Target_io_grpc_netty_Utils { + + @Substitute + static boolean isEpollAvailable() { + return false; + } + + @Substitute + private static Throwable getEpollUnavailabilityCause() { + return null; + } +} + @SuppressWarnings("unused") class GrpcNettySubstitutions { } diff --git a/integration-tests/grpc-plain-text-mutiny/pom.xml b/integration-tests/grpc-plain-text-mutiny/pom.xml new file mode 100644 index 0000000000000..4696971975b71 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/pom.xml @@ -0,0 +1,165 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + + quarkus-integration-test-grpc-plain-text-mutiny + Quarkus - Integration Tests - gRPC - Plain Text with Mutiny + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-mutiny + + + io.quarkus + quarkus-grpc + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-grpc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-mutiny-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + generate-code + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${native.surefire.skip} + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java new file mode 100644 index 0000000000000..9028734a3105b --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java @@ -0,0 +1,43 @@ +package io.quarkus.grpc.examples.hello; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import examples.GreeterGrpc; +import examples.HelloReply; +import examples.HelloRequest; +import examples.MutinyGreeterGrpc; +import io.quarkus.grpc.runtime.annotations.GrpcService; +import io.smallrye.mutiny.Uni; + +@Path("/hello") +public class HelloWorldEndpoint { + + @Inject + @GrpcService("hello") + GreeterGrpc.GreeterBlockingStub blockingHelloService; + @Inject + @GrpcService("hello") + MutinyGreeterGrpc.MutinyGreeterStub mutinyHelloService; + + @GET + @Path("/blocking/{name}") + public String helloBlocking(@PathParam("name") String name) { + HelloReply reply = blockingHelloService.sayHello(HelloRequest.newBuilder().setName(name).build()); + return generateResponse(reply); + + } + + @GET + @Path("/mutiny/{name}") + public Uni helloMutiny(@PathParam("name") String name) { + return mutinyHelloService.sayHello(HelloRequest.newBuilder().setName(name).build()) + .onItem().transform((reply) -> generateResponse(reply)); + } + + public String generateResponse(HelloReply reply) { + return String.format("%s! HelloWorldService has been called %d number of times.", reply.getMessage(), reply.getCount()); + } +} diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java new file mode 100644 index 0000000000000..667afdbe987e6 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java @@ -0,0 +1,24 @@ +package io.quarkus.grpc.examples.hello; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Singleton; + +import examples.HelloReply; +import examples.HelloRequest; +import examples.MutinyGreeterGrpc; +import io.smallrye.mutiny.Uni; + +@Singleton +public class HelloWorldService extends MutinyGreeterGrpc.GreeterImplBase { + + AtomicInteger counter = new AtomicInteger(); + + @Override + public Uni sayHello(HelloRequest request) { + int count = counter.incrementAndGet(); + String name = request.getName(); + return Uni.createFrom().item("Hello " + name) + .map(res -> HelloReply.newBuilder().setMessage(res).setCount(count).build()); + } +} diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/proto/helloworld.proto b/integration-tests/grpc-plain-text-mutiny/src/main/proto/helloworld.proto new file mode 100644 index 0000000000000..89ccb456d8308 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/main/proto/helloworld.proto @@ -0,0 +1,54 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "examples"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; + int32 count = 2; +} diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/resources/application.properties b/integration-tests/grpc-plain-text-mutiny/src/main/resources/application.properties new file mode 100644 index 0000000000000..f8a95fc9c4c11 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.grpc.clients.hello.host=localhost \ No newline at end of file diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointIT.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointIT.java new file mode 100644 index 0000000000000..17f195143ebf5 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointIT.java @@ -0,0 +1,8 @@ +package io.quarkus.grpc.examples.hello; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class HelloWorldEndpointIT extends HelloWorldEndpointTest { + +} \ No newline at end of file diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointTest.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointTest.java new file mode 100644 index 0000000000000..2c6c673852346 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointTest.java @@ -0,0 +1,25 @@ +package io.quarkus.grpc.examples.hello; + +import static io.restassured.RestAssured.get; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class HelloWorldEndpointTest { + + @Test + public void testHelloWorldServiceUsingBlockingStub() { + String response = get("/hello/blocking/neo").asString(); + assertThat(response).startsWith("Hello neo"); + } + + @Test + public void testHelloWorldServiceUsingMutinyStub() { + String response = get("/hello/mutiny/neo-mutiny").asString(); + assertThat(response).startsWith("Hello neo-mutiny"); + } + +} diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceIT.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceIT.java new file mode 100644 index 0000000000000..addf2badc869b --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceIT.java @@ -0,0 +1,8 @@ +package io.quarkus.grpc.examples.hello; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class HelloWorldServiceIT extends HelloWorldEndpointTest { + +} \ No newline at end of file diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceTest.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceTest.java new file mode 100644 index 0000000000000..1f775a5177ae8 --- /dev/null +++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceTest.java @@ -0,0 +1,52 @@ +package io.quarkus.grpc.examples.hello; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import examples.GreeterGrpc; +import examples.HelloReply; +import examples.HelloRequest; +import examples.MutinyGreeterGrpc; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class HelloWorldServiceTest { + + private ManagedChannel channel; + + @BeforeEach + public void init() { + channel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); + } + + @AfterEach + public void cleanup() throws InterruptedException { + channel.shutdown(); + channel.awaitTermination(10, TimeUnit.SECONDS); + } + + @Test + public void testHelloWorldServiceUsingBlockingStub() { + GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel); + HelloReply reply = client + .sayHello(HelloRequest.newBuilder().setName("neo-blocking").build()); + assertThat(reply.getMessage()).isEqualTo("Hello neo-blocking"); + } + + @Test + public void testHelloWorldServiceUsingMutinyStub() { + HelloReply reply = MutinyGreeterGrpc.newMutinyStub(channel) + .sayHello(HelloRequest.newBuilder().setName("neo-blocking").build()) + .await().atMost(Duration.ofSeconds(5)); + assertThat(reply.getMessage()).isEqualTo("Hello neo-blocking"); + } + +} diff --git a/integration-tests/grpc-plain-text/pom.xml b/integration-tests/grpc-plain-text/pom.xml index 9a3f60042fe46..4f8f259956c22 100644 --- a/integration-tests/grpc-plain-text/pom.xml +++ b/integration-tests/grpc-plain-text/pom.xml @@ -88,6 +88,28 @@ + + + + io.quarkus + quarkus-infinispan-client + + + io.quarkus + quarkus-infinispan-client-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java b/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java index 667afdbe987e6..aa1967bbd877c 100644 --- a/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java +++ b/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java @@ -4,21 +4,22 @@ import javax.inject.Singleton; +import examples.GreeterGrpc; import examples.HelloReply; import examples.HelloRequest; -import examples.MutinyGreeterGrpc; -import io.smallrye.mutiny.Uni; +import io.grpc.stub.StreamObserver; @Singleton -public class HelloWorldService extends MutinyGreeterGrpc.GreeterImplBase { +public class HelloWorldService extends GreeterGrpc.GreeterImplBase { AtomicInteger counter = new AtomicInteger(); @Override - public Uni sayHello(HelloRequest request) { + public void sayHello(HelloRequest request, StreamObserver responseObserver) { int count = counter.incrementAndGet(); String name = request.getName(); - return Uni.createFrom().item("Hello " + name) - .map(res -> HelloReply.newBuilder().setMessage(res).setCount(count).build()); + String res = "Hello " + name; + responseObserver.onNext(HelloReply.newBuilder().setMessage(res).setCount(count).build()); + responseObserver.onCompleted(); } } diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 0795b67316672..99c679702a1d3 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -144,8 +144,9 @@ mailer - grpc-plain-text grpc-tls + grpc-plain-text + grpc-plain-text-mutiny grpc-mutual-auth grpc-streaming grpc-interceptors