Skip to content

Commit

Permalink
Initial upload of a GRPC implementation for Helidon (#259)
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Bair <rbair23@users.noreply.github.com>
Signed-off-by: litt <austin@swirldslabs.com>
Co-authored-by: litt <austin@swirldslabs.com>
  • Loading branch information
rbair23 and litt authored Sep 19, 2024
1 parent aa12028 commit 61765cc
Show file tree
Hide file tree
Showing 31 changed files with 2,971 additions and 10 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/flow-pull-request-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ jobs:
secrets:
gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }}
gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }}
codacy-project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}

integration-tests:
name: Integration Tests
Expand All @@ -51,4 +50,3 @@ jobs:
secrets:
gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }}
gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }}
codacy-project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
1 change: 0 additions & 1 deletion .github/workflows/zxf-build-application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@ jobs:
secrets:
gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }}
gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }}
codacy-project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
9 changes: 7 additions & 2 deletions pbj-core/gradle/modules.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
com.github.spotbugs.annotations=com.github.spotbugs:spotbugs-annotations
com.google.protobuf=com.google.protobuf:protobuf-java
org.antlr.antlr4.runtime=org.antlr:antlr4-runtime
com.google.protobuf.util=com.google.protobuf:protobuf-java-util
io.grpc.netty=io.grpc:grpc-netty
io.grpc.protobuf=io.grpc:grpc-protobuf
io.grpc.stub=io.grpc:grpc-stub
java.annotation=javax.annotation:javax.annotation-api
io.helidon.codegen.apt=io.helidon.codegen:helidon-codegen-apt
io.helidon.builder.codegen=io.helidon.builder:helidon-builder-codegen
10 changes: 10 additions & 0 deletions pbj-core/pbj-grpc-helidon-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# pbj-grpc-helidon-config

This project contains just the configuration definition for the
`pbj-grpc-helidon` module.

Helidon modules require a "config blueprint". An annotation processor takes that
"blueprint" and generates some metadata in META-INF and some code. The module
then needs to compile against the generated code. Since Gradle is not capable of
doing this in a single build, we have to split the configuration into a separate
module.
32 changes: 32 additions & 0 deletions pbj-core/pbj-grpc-helidon-config/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins { id("com.hedera.pbj.conventions") }

mainModuleInfo {
annotationProcessor("io.helidon.common.features.processor")
annotationProcessor("io.helidon.config.metadata.processor")
annotationProcessor("io.helidon.codegen.apt")
annotationProcessor("io.helidon.builder.codegen")
}

tasks.named("javadoc").configure { enabled = false }

publishing {
publications.withType<MavenPublication>().configureEach {
pom { description.set("Configuration for a Helidon gRPC plugin for PBJ") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.pbj.grpc.helidon.config;

import io.helidon.builder.api.Option;
import io.helidon.builder.api.Prototype;
import io.helidon.webserver.spi.ProtocolConfig;

@Prototype.Blueprint
@Prototype.Configured
@Prototype.Provides(ProtocolConfig.class)
interface PbjConfigBlueprint extends ProtocolConfig {
/**
* Default maximum message size in bytes ({@value}).
*
* @see #maxMessageSizeBytes()
*/
int DEFAULT_MAX_MESSAGE_SIZE_BYTES = 1024 * 10; // 10KB

/**
* Maximum size of any message in bytes. Defaults to {@value #DEFAULT_MAX_MESSAGE_SIZE_BYTES}.
*
* @return the maximum number of bytes a single message can be
*/
@Option.DefaultInt(DEFAULT_MAX_MESSAGE_SIZE_BYTES)
@Option.Configured
int maxMessageSizeBytes();

/**
* Protocol configuration type.
*
* @return type of this configuration
*/
default String type() {
return "pbj";
}
}
21 changes: 21 additions & 0 deletions pbj-core/pbj-grpc-helidon-config/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import io.helidon.common.features.api.Feature;
import io.helidon.common.features.api.HelidonFlavor;
import io.helidon.common.features.api.Preview;

@Preview
@Feature(
value = "PBJConfig",
description = "WebServer gRPC-PBJ Config",
in = HelidonFlavor.SE,
path = {"WebServer", "PBJ"})
@SuppressWarnings({"requires-automatic"})
module com.hedera.pbj.grpc.helidon.config {
requires com.hedera.pbj.runtime;
requires io.helidon.webserver.http2;
requires io.helidon.webserver;
requires static io.helidon.common.features.api;
requires static io.helidon.common.features.processor;
requires static io.helidon.config.metadata.processor;

exports com.hedera.pbj.grpc.helidon.config;
}
4 changes: 4 additions & 0 deletions pbj-core/pbj-grpc-helidon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# PBJ GRPC Helidon

This project produces a module for Helidon that enables native support for PBJ
gRPC services.
51 changes: 51 additions & 0 deletions pbj-core/pbj-grpc-helidon/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("com.hedera.pbj.conventions")
id("com.hedera.pbj.protoc") // protobuf plugin is only used for tests
}

// These annotation processors are used to generate config and other files that Helidon needs
mainModuleInfo {
annotationProcessor("io.helidon.common.features.processor")
annotationProcessor("io.helidon.codegen.apt")
annotationProcessor("io.helidon.builder.codegen")
}

testModuleInfo {
requires("org.assertj.core")
requires("org.junit.jupiter.api")
requires("org.junit.jupiter.params")
requires("io.helidon.webclient")
requires("io.helidon.webserver")
requires("io.helidon.webserver.http2")
requires("io.helidon.webclient.http2")
requires("com.google.protobuf.util")
requires("io.grpc.protobuf")
requires("io.grpc.netty")
requires("io.grpc.stub")
requiresStatic("com.github.spotbugs.annotations")
requiresStatic("java.annotation")
}

tasks.named("compileJava") { dependsOn(":pbj-runtime:jar") }

publishing {
publications.withType<MavenPublication>().configureEach {
pom { description.set("A Helidon gRPC plugin with PBJ") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.pbj.grpc.helidon;

import com.hedera.pbj.runtime.grpc.GrpcStatus;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.concurrent.ScheduledFuture;

/**
* A gRPC client may specify a 'deadline' for a request, which is measured in elapsed time from the
* start of the request. If the deadline is exceeded, then the request will fail with {@link
* GrpcStatus#DEADLINE_EXCEEDED}. An implementation of this interface is responsible for detecting
* when the deadline has been exceeded, and invoking a callback to handle the failure.
*/
interface DeadlineDetector {
/**
* Schedule a callback to be invoked when the deadline has been exceeded. Please note that no
* operating system can actually measure elapsed time with nanosecond precision, so the actual
* deadline may be exceeded by a small amount of time measuring in the microseconds or even
* milliseconds.
*
* @param deadlineNanos The deadline, in nanoseconds, from now.
* @param onDeadlineExceeded The callback to invoke when the deadline has been exceeded.
* @return A {@link ScheduledFuture} that can be used to cancel the deadline.
*/
@NonNull
ScheduledFuture<?> scheduleDeadline(long deadlineNanos, @NonNull Runnable onDeadlineExceeded);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.pbj.grpc.helidon;

import static com.hedera.pbj.runtime.grpc.ServiceInterface.RequestOptions.APPLICATION_GRPC;
import static io.helidon.http.HeaderValues.createCached;

import com.hedera.pbj.runtime.grpc.GrpcStatus;
import edu.umd.cs.findbugs.annotations.NonNull;
import io.helidon.http.Header;
import io.helidon.http.HeaderName;
import io.helidon.http.HeaderNames;
import io.helidon.http.HttpMediaType;

/** Constants for gRPC related HTTP2 Headers. */
final class GrpcHeaders {
static final HeaderName GRPC_TIMEOUT = HeaderNames.create("grpc-timeout");
static final HeaderName GRPC_ENCODING = HeaderNames.create("grpc-encoding");
static final HeaderName GRPC_ACCEPT_ENCODING = HeaderNames.create("grpc-accept-encoding");
static final HeaderName GRPC_MESSAGE = HeaderNames.createFromLowercase("grpc-message");
static final HeaderName GRPC_STATUS = HeaderNames.createFromLowercase("grpc-status");
static final HttpMediaType APPLICATION_GRPC_PROTO_TYPE = HttpMediaType.create(APPLICATION_GRPC);

// A set of Headers with pre-cached values for the different "grpc-status" header.
static final Header OK = createCached(GRPC_STATUS, GrpcStatus.OK.ordinal());
static final Header CANCELLED = createCached(GRPC_STATUS, GrpcStatus.CANCELLED.ordinal());
static final Header UNKNOWN = createCached(GRPC_STATUS, GrpcStatus.UNKNOWN.ordinal());
static final Header INVALID_ARGUMENT =
createCached(GRPC_STATUS, GrpcStatus.INVALID_ARGUMENT.ordinal());
static final Header DEADLINE_EXCEEDED =
createCached(GRPC_STATUS, GrpcStatus.DEADLINE_EXCEEDED.ordinal());
static final Header NOT_FOUND = createCached(GRPC_STATUS, GrpcStatus.NOT_FOUND.ordinal());
static final Header ALREADY_EXISTS =
createCached(GRPC_STATUS, GrpcStatus.ALREADY_EXISTS.ordinal());
static final Header PERMISSION_DENIED =
createCached(GRPC_STATUS, GrpcStatus.PERMISSION_DENIED.ordinal());
static final Header RESOURCE_EXHAUSTED =
createCached(GRPC_STATUS, GrpcStatus.RESOURCE_EXHAUSTED.ordinal());
static final Header FAILED_PRECONDITION =
createCached(GRPC_STATUS, GrpcStatus.FAILED_PRECONDITION.ordinal());
static final Header ABORTED = createCached(GRPC_STATUS, GrpcStatus.ABORTED.ordinal());
static final Header OUT_OF_RANGE = createCached(GRPC_STATUS, GrpcStatus.OUT_OF_RANGE.ordinal());
static final Header UNIMPLEMENTED =
createCached(GRPC_STATUS, GrpcStatus.UNIMPLEMENTED.ordinal());
static final Header INTERNAL = createCached(GRPC_STATUS, GrpcStatus.INTERNAL.ordinal());
static final Header UNAVAILABLE = createCached(GRPC_STATUS, GrpcStatus.UNAVAILABLE.ordinal());
static final Header DATA_LOSS = createCached(GRPC_STATUS, GrpcStatus.DATA_LOSS.ordinal());
static final Header UNAUTHENTICATED =
createCached(GRPC_STATUS, GrpcStatus.UNAUTHENTICATED.ordinal());

private GrpcHeaders() {
// prevent instantiation
}

/**
* Maps the given {@link #GRPC_STATUS} to a corresponding {@link Header}.
*
* @param status The status.
* @return The corresponding {@link Header}.
*/
@NonNull
static Header header(@NonNull final GrpcStatus status) {
return switch (status) {
case OK -> OK;
case CANCELLED -> CANCELLED;
case UNKNOWN -> UNKNOWN;
case INVALID_ARGUMENT -> INVALID_ARGUMENT;
case DEADLINE_EXCEEDED -> DEADLINE_EXCEEDED;
case NOT_FOUND -> NOT_FOUND;
case ALREADY_EXISTS -> ALREADY_EXISTS;
case PERMISSION_DENIED -> PERMISSION_DENIED;
case RESOURCE_EXHAUSTED -> RESOURCE_EXHAUSTED;
case FAILED_PRECONDITION -> FAILED_PRECONDITION;
case ABORTED -> ABORTED;
case OUT_OF_RANGE -> OUT_OF_RANGE;
case UNIMPLEMENTED -> UNIMPLEMENTED;
case INTERNAL -> INTERNAL;
case UNAVAILABLE -> UNAVAILABLE;
case DATA_LOSS -> DATA_LOSS;
case UNAUTHENTICATED -> UNAUTHENTICATED;
};
}
}
Loading

0 comments on commit 61765cc

Please sign in to comment.