diff --git a/README.md b/README.md index cb38ad66394..f56aebfb3ac 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.66.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.66.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.67.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.67.1/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.66.0 + 1.67.1 runtime io.grpc grpc-protobuf - 1.66.0 + 1.67.1 io.grpc grpc-stub - 1.66.0 + 1.67.1 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.66.0' -implementation 'io.grpc:grpc-protobuf:1.66.0' -implementation 'io.grpc:grpc-stub:1.66.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.67.1' +implementation 'io.grpc:grpc-protobuf:1.67.1' +implementation 'io.grpc:grpc-stub:1.67.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.66.0' -implementation 'io.grpc:grpc-protobuf-lite:1.66.0' -implementation 'io.grpc:grpc-stub:1.66.0' +implementation 'io.grpc:grpc-okhttp:1.67.1' +implementation 'io.grpc:grpc-protobuf-lite:1.67.1' +implementation 'io.grpc:grpc-stub:1.67.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.66.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.67.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.67.1:exe:${os.detected.classifier} @@ -161,7 +161,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.67.1' } } generateProtoTasks { @@ -194,7 +194,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.67.1' } } generateProtoTasks { diff --git a/RELEASING.md b/RELEASING.md index bb1b77d0557..0add8991746 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -65,7 +65,7 @@ would be used to create all `v1.7` tags (e.g. `v1.7.0`, `v1.7.1`). ```bash git fetch upstream git checkout -b v$MAJOR.$MINOR.x \ - $(git log --pretty=format:%H --grep "^Start $MAJOR.$((MINOR+1)).0 development cycle$" upstream/master)^ + $(git log --pretty=format:%H --grep "^Start $MAJOR.$((MINOR+1)).0 development cycle" upstream/master)^ git push upstream v$MAJOR.$MINOR.x ``` 5. Continue with Google-internal steps at go/grpc-java/releasing, but stop @@ -132,7 +132,9 @@ Tagging the Release compiler/src/test{,Lite}/golden/Test{,Deprecated}Service.java.txt ./gradlew build git commit -a -m "Bump version to $MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT" + git push -u origin release-v$MAJOR.$MINOR.$PATCH ``` + Raise a PR and set the base branch of the PR to v$MAJOR.$MINOR.x of the upstream grpc-java repo. 6. Go through PR review and push the release tag and updated release branch to GitHub (DO NOT click the merge button on the GitHub page): diff --git a/android/src/main/java/io/grpc/android/UdsChannelBuilder.java b/android/src/main/java/io/grpc/android/UdsChannelBuilder.java index e2dc7232378..7d41301704c 100644 --- a/android/src/main/java/io/grpc/android/UdsChannelBuilder.java +++ b/android/src/main/java/io/grpc/android/UdsChannelBuilder.java @@ -68,12 +68,15 @@ public static ManagedChannelBuilder forPath(String path, Namespace namespace) throw new UnsupportedOperationException("OkHttpChannelBuilder not found on the classpath"); } try { - // Target 'dns:///localhost' is unused, but necessary as an argument for OkHttpChannelBuilder. + // Target 'dns:///127.0.0.1' is unused, but necessary as an argument for OkHttpChannelBuilder. + // An IP address is used instead of localhost to avoid a DNS lookup (see #11442). This should + // work even if IPv4 is unavailable, as the DNS resolver doesn't need working IPv4 to parse an + // IPv4 address. Unavailable IPv4 fails when we connect(), not at resolution time. // TLS is unsupported because Conscrypt assumes the platform Socket implementation to improve // performance by using the file descriptor directly. Object o = OKHTTP_CHANNEL_BUILDER_CLASS .getMethod("forTarget", String.class, ChannelCredentials.class) - .invoke(null, "dns:///localhost", InsecureChannelCredentials.create()); + .invoke(null, "dns:///127.0.0.1", InsecureChannelCredentials.create()); ManagedChannelBuilder builder = OKHTTP_CHANNEL_BUILDER_CLASS.cast(o); OKHTTP_CHANNEL_BUILDER_CLASS .getMethod("socketFactory", SocketFactory.class) diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 0fbce5fa5be..6d74006b396 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -121,6 +121,12 @@ public abstract class LoadBalancer { HEALTH_CONSUMER_LISTENER_ARG_KEY = LoadBalancer.CreateSubchannelArgs.Key.create("internal:health-check-consumer-listener"); + @Internal + public static final LoadBalancer.CreateSubchannelArgs.Key + DISABLE_SUBCHANNEL_RECONNECT_KEY = + LoadBalancer.CreateSubchannelArgs.Key.createWithDefault( + "internal:disable-subchannel-reconnect", Boolean.FALSE); + @Internal public static final Attributes.Key HAS_HEALTH_PRODUCER_LISTENER_KEY = diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java index bfb9c2a43a1..b9590ab5d5a 100644 --- a/api/src/main/java/io/grpc/NameResolver.java +++ b/api/src/main/java/io/grpc/NameResolver.java @@ -20,13 +20,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Objects; import com.google.errorprone.annotations.InlineMe; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URI; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -95,7 +95,8 @@ public void onError(Status error) { @Override public void onResult(ResolutionResult resolutionResult) { - listener.onAddresses(resolutionResult.getAddresses(), resolutionResult.getAttributes()); + listener.onAddresses(resolutionResult.getAddressesOrError().getValue(), + resolutionResult.getAttributes()); } }); } @@ -218,19 +219,21 @@ public abstract static class Listener2 implements Listener { @Override @Deprecated @InlineMe( - replacement = "this.onResult(ResolutionResult.newBuilder().setAddresses(servers)" - + ".setAttributes(attributes).build())", - imports = "io.grpc.NameResolver.ResolutionResult") + replacement = "this.onResult2(ResolutionResult.newBuilder().setAddressesOrError(" + + "StatusOr.fromValue(servers)).setAttributes(attributes).build())", + imports = {"io.grpc.NameResolver.ResolutionResult", "io.grpc.StatusOr"}) public final void onAddresses( List servers, @ResolutionResultAttr Attributes attributes) { // TODO(jihuncho) need to promote Listener2 if we want to use ConfigOrError - onResult( - ResolutionResult.newBuilder().setAddresses(servers).setAttributes(attributes).build()); + onResult2( + ResolutionResult.newBuilder().setAddressesOrError( + StatusOr.fromValue(servers)).setAttributes(attributes).build()); } /** * Handles updates on resolved addresses and attributes. If - * {@link ResolutionResult#getAddresses()} is empty, {@link #onError(Status)} will be called. + * {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be + * called. * * @param resolutionResult the resolved server addresses, attributes, and Service Config. * @since 1.21.0 @@ -584,17 +587,17 @@ public abstract static class ServiceConfigParser { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770") public static final class ResolutionResult { - private final List addresses; + private final StatusOr> addressesOrError; @ResolutionResultAttr private final Attributes attributes; @Nullable private final ConfigOrError serviceConfig; ResolutionResult( - List addresses, + StatusOr> addressesOrError, @ResolutionResultAttr Attributes attributes, ConfigOrError serviceConfig) { - this.addresses = Collections.unmodifiableList(new ArrayList<>(addresses)); + this.addressesOrError = addressesOrError; this.attributes = checkNotNull(attributes, "attributes"); this.serviceConfig = serviceConfig; } @@ -615,7 +618,7 @@ public static Builder newBuilder() { */ public Builder toBuilder() { return newBuilder() - .setAddresses(addresses) + .setAddressesOrError(addressesOrError) .setAttributes(attributes) .setServiceConfig(serviceConfig); } @@ -624,9 +627,20 @@ public Builder toBuilder() { * Gets the addresses resolved by name resolution. * * @since 1.21.0 + * @deprecated Will be superseded by getAddressesOrError */ + @Deprecated public List getAddresses() { - return addresses; + return addressesOrError.getValue(); + } + + /** + * Gets the addresses resolved by name resolution or the error in doing so. + * + * @since 1.65.0 + */ + public StatusOr> getAddressesOrError() { + return addressesOrError; } /** @@ -652,11 +666,11 @@ public ConfigOrError getServiceConfig() { @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("addresses", addresses) - .add("attributes", attributes) - .add("serviceConfig", serviceConfig) - .toString(); + ToStringHelper stringHelper = MoreObjects.toStringHelper(this); + stringHelper.add("addressesOrError", addressesOrError.toString()); + stringHelper.add("attributes", attributes); + stringHelper.add("serviceConfigOrError", serviceConfig); + return stringHelper.toString(); } /** @@ -668,7 +682,7 @@ public boolean equals(Object obj) { return false; } ResolutionResult that = (ResolutionResult) obj; - return Objects.equal(this.addresses, that.addresses) + return Objects.equal(this.addressesOrError, that.addressesOrError) && Objects.equal(this.attributes, that.attributes) && Objects.equal(this.serviceConfig, that.serviceConfig); } @@ -678,7 +692,7 @@ public boolean equals(Object obj) { */ @Override public int hashCode() { - return Objects.hashCode(addresses, attributes, serviceConfig); + return Objects.hashCode(addressesOrError, attributes, serviceConfig); } /** @@ -688,7 +702,8 @@ public int hashCode() { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770") public static final class Builder { - private List addresses = Collections.emptyList(); + private StatusOr> addresses = + StatusOr.fromValue(Collections.emptyList()); private Attributes attributes = Attributes.EMPTY; @Nullable private ConfigOrError serviceConfig; @@ -700,9 +715,21 @@ public static final class Builder { * Sets the addresses resolved by name resolution. This field is required. * * @since 1.21.0 + * @deprecated Will be superseded by setAddressesOrError */ + @Deprecated public Builder setAddresses(List addresses) { - this.addresses = addresses; + setAddressesOrError(StatusOr.fromValue(addresses)); + return this; + } + + /** + * Sets the addresses resolved by name resolution or the error in doing so. This field is + * required. + * @param addresses Resolved addresses or an error in resolving addresses + */ + public Builder setAddressesOrError(StatusOr> addresses) { + this.addresses = checkNotNull(addresses, "StatusOr addresses cannot be null."); return this; } diff --git a/api/src/main/java/io/grpc/StatusOr.java b/api/src/main/java/io/grpc/StatusOr.java new file mode 100644 index 00000000000..8a88d9e62d0 --- /dev/null +++ b/api/src/main/java/io/grpc/StatusOr.java @@ -0,0 +1,102 @@ +/* + * Copyright 2024 The gRPC Authors + * + * 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 io.grpc; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; +import javax.annotation.Nullable; + +/** Either a Status or a value. */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11563") +public class StatusOr { + private StatusOr(Status status, T value) { + this.status = status; + this.value = value; + } + + /** Construct from a value. */ + public static StatusOr fromValue(@Nullable T value) { + StatusOr result = new StatusOr(null, value); + return result; + } + + /** Construct from a non-Ok status. */ + public static StatusOr fromStatus(Status status) { + StatusOr result = new StatusOr(checkNotNull(status, "status"), null); + checkArgument(!status.isOk(), "cannot use OK status: %s", status); + return result; + } + + /** Returns whether there is a value. */ + public boolean hasValue() { + return status == null; + } + + /** + * Returns the value if set or throws exception if there is no value set. This method is meant + * to be called after checking the return value of hasValue() first. + */ + public @Nullable T getValue() { + if (status != null) { + throw new IllegalStateException("No value present."); + } + return value; + } + + /** Returns the status. If there is a value (which can be null), returns OK. */ + public Status getStatus() { + return status == null ? Status.OK : status; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof StatusOr)) { + return false; + } + StatusOr otherStatus = (StatusOr) other; + if (hasValue() != otherStatus.hasValue()) { + return false; + } + if (hasValue()) { + return Objects.equal(value, otherStatus.value); + } + return Objects.equal(status, otherStatus.status); + } + + @Override + public int hashCode() { + return Objects.hashCode(status, value); + } + + @Override + public String toString() { + ToStringHelper stringHelper = MoreObjects.toStringHelper(this); + if (status == null) { + stringHelper.add("value", value); + } else { + stringHelper.add("error", status); + } + return stringHelper.toString(); + } + + private final Status status; + private final T value; +} diff --git a/api/src/test/java/io/grpc/NameResolverTest.java b/api/src/test/java/io/grpc/NameResolverTest.java index f825de354af..1bc32ee7b1d 100644 --- a/api/src/test/java/io/grpc/NameResolverTest.java +++ b/api/src/test/java/io/grpc/NameResolverTest.java @@ -17,20 +17,43 @@ package io.grpc; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import com.google.common.base.Objects; +import io.grpc.NameResolver.ConfigOrError; +import io.grpc.NameResolver.Listener2; +import io.grpc.NameResolver.ResolutionResult; import io.grpc.NameResolver.ServiceConfigParser; import java.lang.Thread.UncaughtExceptionHandler; +import java.net.SocketAddress; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** Unit tests for the inner classes in {@link NameResolver}. */ @RunWith(JUnit4.class) public class NameResolverTest { + private static final List ADDRESSES = + Collections.singletonList( + new EquivalentAddressGroup(new FakeSocketAddress("fake-address-1"), Attributes.EMPTY)); + private static final Attributes.Key YOLO_KEY = Attributes.Key.create("yolo"); + private static Attributes ATTRIBUTES = Attributes.newBuilder() + .set(YOLO_KEY, "To be, or not to be?").build(); + private static ConfigOrError CONFIG = ConfigOrError.fromConfig("foo"); + + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); private final int defaultPort = 293; private final ProxyDetector proxyDetector = mock(ProxyDetector.class); private final SynchronizationContext syncContext = @@ -41,6 +64,7 @@ public class NameResolverTest { private final ChannelLogger channelLogger = mock(ChannelLogger.class); private final Executor executor = Executors.newSingleThreadExecutor(); private final String overrideAuthority = "grpc.io"; + @Mock NameResolver.Listener mockListener; @Test public void args() { @@ -80,4 +104,90 @@ private NameResolver.Args createArgs() { .setOverrideAuthority(overrideAuthority) .build(); } + + @Test + @SuppressWarnings("deprecation") + public void startOnOldListener_wrapperListener2UsedToStart() { + final Listener2[] listener2 = new Listener2[1]; + NameResolver nameResolver = new NameResolver() { + @Override + public String getServiceAuthority() { + return null; + } + + @Override + public void shutdown() {} + + @Override + public void start(Listener2 listener2Arg) { + listener2[0] = listener2Arg; + } + }; + nameResolver.start(mockListener); + + listener2[0].onResult(ResolutionResult.newBuilder().setAddresses(ADDRESSES) + .setAttributes(ATTRIBUTES).build()); + verify(mockListener).onAddresses(eq(ADDRESSES), eq(ATTRIBUTES)); + listener2[0].onError(Status.CANCELLED); + verify(mockListener).onError(Status.CANCELLED); + } + + @Test + @SuppressWarnings({"deprecation", "InlineMeInliner"}) + public void listener2AddressesToListener2ResolutionResultConversion() { + final ResolutionResult[] resolutionResult = new ResolutionResult[1]; + NameResolver.Listener2 listener2 = new Listener2() { + @Override + public void onResult(ResolutionResult resolutionResultArg) { + resolutionResult[0] = resolutionResultArg; + } + + @Override + public void onError(Status error) {} + }; + + listener2.onAddresses(ADDRESSES, ATTRIBUTES); + + assertThat(resolutionResult[0].getAddressesOrError().getValue()).isEqualTo(ADDRESSES); + assertThat(resolutionResult[0].getAttributes()).isEqualTo(ATTRIBUTES); + } + + @Test + public void resolutionResult_toString_addressesAttributesAndConfig() { + ResolutionResult resolutionResult = ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromValue(ADDRESSES)) + .setAttributes(ATTRIBUTES) + .setServiceConfig(CONFIG) + .build(); + + assertThat(resolutionResult.toString()).isEqualTo( + "ResolutionResult{addressesOrError=StatusOr{value=" + + "[[[FakeSocketAddress-fake-address-1]/{}]]}, attributes={yolo=To be, or not to be?}, " + + "serviceConfigOrError=ConfigOrError{config=foo}}"); + } + + @Test + public void resolutionResult_hashCode() { + ResolutionResult resolutionResult = ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromValue(ADDRESSES)) + .setAttributes(ATTRIBUTES) + .setServiceConfig(CONFIG) + .build(); + + assertThat(resolutionResult.hashCode()).isEqualTo( + Objects.hashCode(StatusOr.fromValue(ADDRESSES), ATTRIBUTES, CONFIG)); + } + + private static class FakeSocketAddress extends SocketAddress { + final String name; + + FakeSocketAddress(String name) { + this.name = name; + } + + @Override + public String toString() { + return "FakeSocketAddress-" + name; + } + } } diff --git a/api/src/test/java/io/grpc/StatusOrTest.java b/api/src/test/java/io/grpc/StatusOrTest.java new file mode 100644 index 00000000000..f63a314a2bb --- /dev/null +++ b/api/src/test/java/io/grpc/StatusOrTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 The gRPC Authors + * + * 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 io.grpc; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link StatusOr}. **/ +@RunWith(JUnit4.class) +public class StatusOrTest { + + @Test + public void getValue_throwsIfNoValuePresent() { + try { + StatusOr.fromStatus(Status.ABORTED).getValue(); + + fail("Expected exception."); + } catch (IllegalStateException expected) { } + } + + @Test + @SuppressWarnings("TruthIncompatibleType") + public void equals_differentValueTypes() { + assertThat(StatusOr.fromValue(1)).isNotEqualTo(StatusOr.fromValue("1")); + } + + @Test + public void equals_differentValues() { + assertThat(StatusOr.fromValue(1)).isNotEqualTo(StatusOr.fromValue(2)); + } + + @Test + public void equals_sameValues() { + assertThat(StatusOr.fromValue(1)).isEqualTo(StatusOr.fromValue(1)); + } + + @Test + public void equals_differentStatuses() { + assertThat(StatusOr.fromStatus(Status.ABORTED)).isNotEqualTo( + StatusOr.fromStatus(Status.CANCELLED)); + } + + @Test + public void equals_sameStatuses() { + assertThat(StatusOr.fromStatus(Status.ABORTED)).isEqualTo(StatusOr.fromStatus(Status.ABORTED)); + } + + @Test + public void toString_value() { + assertThat(StatusOr.fromValue(1).toString()).isEqualTo("StatusOr{value=1}"); + } + + @Test + public void toString_nullValue() { + assertThat(StatusOr.fromValue(null).toString()).isEqualTo("StatusOr{value=null}"); + } + + @Test + public void toString_errorStatus() { + assertThat(StatusOr.fromStatus(Status.ABORTED).toString()).isEqualTo( + "StatusOr{error=Status{code=ABORTED, description=null, cause=null}}"); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 740f534e136..6afe010de4d 100644 --- a/build.gradle +++ b/build.gradle @@ -500,3 +500,4 @@ configurations { } tasks.register('checkForUpdates', CheckForUpdatesTask, project.configurations.checkForUpdates, "libs") + diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index df51d6f2c5c..b59de833d7c 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -32,6 +32,7 @@ import io.grpc.ProxiedSocketAddress; import io.grpc.ProxyDetector; import io.grpc.Status; +import io.grpc.StatusOr; import io.grpc.SynchronizationContext; import io.grpc.internal.SharedResourceHolder.Resource; import java.io.IOException; @@ -59,7 +60,7 @@ * A DNS-based {@link NameResolver}. * *

Each {@code A} or {@code AAAA} record emits an {@link EquivalentAddressGroup} in the list - * passed to {@link NameResolver.Listener2#onResult(ResolutionResult)}. + * passed to {@link NameResolver.Listener2#onResult2(ResolutionResult)}. * * @see DnsNameResolverProvider */ @@ -313,15 +314,20 @@ public void run() { if (logger.isLoggable(Level.FINER)) { logger.finer("Using proxy address " + proxiedAddr); } - resolutionResultBuilder.setAddresses(Collections.singletonList(proxiedAddr)); + resolutionResultBuilder.setAddressesOrError( + StatusOr.fromValue(Collections.singletonList(proxiedAddr))); } else { result = doResolve(false); if (result.error != null) { - savedListener.onError(result.error); + InternalResolutionResult finalResult = result; + syncContext.execute(() -> + savedListener.onResult2(ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromStatus(finalResult.error)) + .build())); return; } if (result.addresses != null) { - resolutionResultBuilder.setAddresses(result.addresses); + resolutionResultBuilder.setAddressesOrError(StatusOr.fromValue(result.addresses)); } if (result.config != null) { resolutionResultBuilder.setServiceConfig(result.config); @@ -334,8 +340,12 @@ public void run() { savedListener.onResult2(resolutionResultBuilder.build()); }); } catch (IOException e) { - savedListener.onError( - Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e)); + syncContext.execute(() -> + savedListener.onResult2(ResolutionResult.newBuilder() + .setAddressesOrError( + StatusOr.fromStatus( + Status.UNAVAILABLE.withDescription( + "Unable to resolve host " + host).withCause(e))).build())); } finally { final boolean succeed = result != null && result.error == null; syncContext.execute(new Runnable() { diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java index 70e42e2f5f1..27a80f7c191 100644 --- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java +++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java @@ -45,6 +45,7 @@ import io.grpc.InternalInstrumented; import io.grpc.InternalLogId; import io.grpc.InternalWithLogId; +import io.grpc.LoadBalancer; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; @@ -77,6 +78,7 @@ final class InternalSubchannel implements InternalInstrumented, Tr private final CallTracer callsTracer; private final ChannelTracer channelTracer; private final ChannelLogger channelLogger; + private final boolean reconnectDisabled; private final List transportFilters; @@ -159,13 +161,15 @@ protected void handleNotInUse() { private volatile Attributes connectedAddressAttributes; - InternalSubchannel(List addressGroups, String authority, String userAgent, - BackoffPolicy.Provider backoffPolicyProvider, - ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor, - Supplier stopwatchSupplier, SynchronizationContext syncContext, Callback callback, - InternalChannelz channelz, CallTracer callsTracer, ChannelTracer channelTracer, - InternalLogId logId, ChannelLogger channelLogger, - List transportFilters) { + InternalSubchannel(LoadBalancer.CreateSubchannelArgs args, String authority, String userAgent, + BackoffPolicy.Provider backoffPolicyProvider, + ClientTransportFactory transportFactory, + ScheduledExecutorService scheduledExecutor, + Supplier stopwatchSupplier, SynchronizationContext syncContext, + Callback callback, InternalChannelz channelz, CallTracer callsTracer, + ChannelTracer channelTracer, InternalLogId logId, + ChannelLogger channelLogger, List transportFilters) { + List addressGroups = args.getAddresses(); Preconditions.checkNotNull(addressGroups, "addressGroups"); Preconditions.checkArgument(!addressGroups.isEmpty(), "addressGroups is empty"); checkListHasNoNulls(addressGroups, "addressGroups contains null entry"); @@ -187,6 +191,7 @@ protected void handleNotInUse() { this.logId = Preconditions.checkNotNull(logId, "logId"); this.channelLogger = Preconditions.checkNotNull(channelLogger, "channelLogger"); this.transportFilters = transportFilters; + this.reconnectDisabled = args.getOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY); } ChannelLogger getChannelLogger() { @@ -289,6 +294,11 @@ public void run() { } gotoState(ConnectivityStateInfo.forTransientFailure(status)); + + if (reconnectDisabled) { + return; + } + if (reconnectPolicy == null) { reconnectPolicy = backoffPolicyProvider.get(); } @@ -337,7 +347,11 @@ private void gotoState(final ConnectivityStateInfo newState) { if (state.getState() != newState.getState()) { Preconditions.checkState(state.getState() != SHUTDOWN, "Cannot transition out of SHUTDOWN to " + newState); - state = newState; + if (reconnectDisabled && newState.getState() == TRANSIENT_FAILURE) { + state = ConnectivityStateInfo.forNonError(IDLE); + } else { + state = newState; + } callback.onStateChange(InternalSubchannel.this, newState); } } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 07dcf9ee7bb..cda4299acec 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -81,6 +81,7 @@ import io.grpc.NameResolverRegistry; import io.grpc.ProxyDetector; import io.grpc.Status; +import io.grpc.StatusOr; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer; @@ -956,7 +957,15 @@ void updateConfigSelector(@Nullable InternalConfigSelector config) { // Must run in SynchronizationContext. void onConfigError() { if (configSelector.get() == INITIAL_PENDING_SELECTOR) { - updateConfigSelector(null); + // Apply Default Service Config if initial name resolution fails. + if (defaultServiceConfig != null) { + updateConfigSelector(defaultServiceConfig.getDefaultConfigSelector()); + lastServiceConfig = defaultServiceConfig; + channelLogger.log(ChannelLogLevel.ERROR, + "Initial Name Resolution error, using default service config"); + } else { + updateConfigSelector(null); + } } } @@ -1475,7 +1484,7 @@ void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { } final InternalSubchannel internalSubchannel = new InternalSubchannel( - addressGroup, + CreateSubchannelArgs.newBuilder().setAddresses(addressGroup).build(), authority, userAgent, backoffPolicyProvider, oobTransportFactory, oobTransportFactory.getScheduledExecutorService(), stopwatchSupplier, syncContext, // All callback methods are run from syncContext @@ -1693,7 +1702,13 @@ public Status onResult2(final ResolutionResult resolutionResult) { return Status.OK; } - List servers = resolutionResult.getAddresses(); + StatusOr> serversOrError = + resolutionResult.getAddressesOrError(); + if (!serversOrError.hasValue()) { + handleErrorInSyncContext(serversOrError.getStatus()); + return serversOrError.getStatus(); + } + List servers = serversOrError.getValue(); channelLogger.log( ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", @@ -1701,10 +1716,10 @@ public Status onResult2(final ResolutionResult resolutionResult) { resolutionResult.getAttributes()); if (lastResolutionState != ResolutionState.SUCCESS) { - channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); + channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", + servers); lastResolutionState = ResolutionState.SUCCESS; } - ConfigOrError configOrError = resolutionResult.getServiceConfig(); InternalConfigSelector resolvedConfigSelector = resolutionResult.getAttributes().get(InternalConfigSelector.KEY); @@ -1780,7 +1795,7 @@ public Status onResult2(final ResolutionResult resolutionResult) { } try { - // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS + // TODO(creamsoup): when `serversOrError` is empty and lastResolutionStateCopy == SUCCESS // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, // lbNeedAddress is not deterministic serviceConfigUpdated = true; @@ -1806,12 +1821,13 @@ public Status onResult2(final ResolutionResult resolutionResult) { } Attributes attributes = attrBuilder.build(); - return helper.lb.tryAcceptResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(attributes) - .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) - .build()); + ResolvedAddresses.Builder resolvedAddresses = ResolvedAddresses.newBuilder() + .setAddresses(serversOrError.getValue()) + .setAttributes(attributes) + .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()); + Status addressAcceptanceStatus = helper.lb.tryAcceptResolvedAddresses( + resolvedAddresses.build()); + return addressAcceptanceStatus; } return Status.OK; } @@ -1907,7 +1923,7 @@ void onNotInUse(InternalSubchannel is) { } final InternalSubchannel internalSubchannel = new InternalSubchannel( - args.getAddresses(), + args, authority(), userAgent, backoffPolicyProvider, diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java index 7da9125087e..48a255472e1 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java @@ -45,6 +45,7 @@ import io.grpc.NameResolverProvider; import io.grpc.NameResolverRegistry; import io.grpc.ProxyDetector; +import io.grpc.StatusOr; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.SocketAddress; @@ -877,9 +878,11 @@ public String getServiceAuthority() { @Override public void start(Listener2 listener) { - listener.onResult( + listener.onResult2( ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList(new EquivalentAddressGroup(address))) + .setAddressesOrError( + StatusOr.fromValue( + Collections.singletonList(new EquivalentAddressGroup(address)))) .setAttributes(Attributes.EMPTY) .build()); } diff --git a/core/src/main/java/io/grpc/internal/MessageDeframer.java b/core/src/main/java/io/grpc/internal/MessageDeframer.java index c8b250c2143..13a01efec0a 100644 --- a/core/src/main/java/io/grpc/internal/MessageDeframer.java +++ b/core/src/main/java/io/grpc/internal/MessageDeframer.java @@ -406,7 +406,8 @@ private void processBody() { // There is no reliable way to get the uncompressed size per message when it's compressed, // because the uncompressed bytes are provided through an InputStream whose total size is // unknown until all bytes are read, and we don't know when it happens. - statsTraceCtx.inboundMessageRead(currentMessageSeqNo, inboundBodyWireSize, -1); + statsTraceCtx.inboundMessageRead(currentMessageSeqNo, inboundBodyWireSize, + (compressedFlag || fullStreamDecompressor != null) ? -1 : inboundBodyWireSize); inboundBodyWireSize = 0; InputStream stream = compressedFlag ? getCompressedBody() : getUncompressedBody(); nextFrame.touch(); diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index bfa462e16e1..6f4794fdd46 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -64,17 +64,26 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer { private int numTf = 0; private boolean firstPass = true; @Nullable - private ScheduledHandle scheduleConnectionTask; + private ScheduledHandle scheduleConnectionTask = null; private ConnectivityState rawConnectivityState = IDLE; private ConnectivityState concludedState = IDLE; - private final boolean enableHappyEyeballs = - PickFirstLoadBalancerProvider.isEnabledHappyEyeballs(); + private final boolean enableHappyEyeballs = !isSerializingRetries() + && PickFirstLoadBalancerProvider.isEnabledHappyEyeballs(); private boolean notAPetiolePolicy = true; // means not under a petiole policy + private final BackoffPolicy.Provider bkoffPolProvider = new ExponentialBackoffPolicy.Provider(); + private BackoffPolicy reconnectPolicy; + @Nullable + private ScheduledHandle reconnectTask = null; + private final boolean serializingRetries = isSerializingRetries(); PickFirstLeafLoadBalancer(Helper helper) { this.helper = checkNotNull(helper, "helper"); } + static boolean isSerializingRetries() { + return GrpcUtil.getFlag("GRPC_SERIALIZE_RETRIES", false); + } + @Override public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { if (rawConnectivityState == SHUTDOWN) { @@ -225,9 +234,10 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo return; } - if (newState == IDLE) { + if (newState == IDLE && subchannelData.state == READY) { helper.refreshNameResolution(); } + // If we are transitioning from a TRANSIENT_FAILURE to CONNECTING or IDLE we ignore this state // transition and still keep the LB in TRANSIENT_FAILURE state. This is referred to as "sticky // transient failure". Only a subchannel state change to READY will get the LB out of @@ -277,6 +287,8 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo if (addressIndex.increment()) { cancelScheduleTask(); requestConnection(); // is recursive so might hit the end of the addresses + } else { + scheduleBackoff(); } } @@ -304,6 +316,39 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo } } + /** + * Only called after all addresses attempted and failed (TRANSIENT_FAILURE). + */ + private void scheduleBackoff() { + if (!serializingRetries) { + return; + } + + class EndOfCurrentBackoff implements Runnable { + @Override + public void run() { + reconnectTask = null; + addressIndex.reset(); + requestConnection(); + } + } + + // Just allow the previous one to trigger when ready if we're already in backoff + if (reconnectTask != null) { + return; + } + + if (reconnectPolicy == null) { + reconnectPolicy = bkoffPolProvider.get(); + } + long delayNanos = reconnectPolicy.nextBackoffNanos(); + reconnectTask = helper.getSynchronizationContext().schedule( + new EndOfCurrentBackoff(), + delayNanos, + TimeUnit.NANOSECONDS, + helper.getScheduledExecutorService()); + } + private void updateHealthCheckedState(SubchannelData subchannelData) { if (subchannelData.state != READY) { return; @@ -337,6 +382,11 @@ public void shutdown() { rawConnectivityState = SHUTDOWN; concludedState = SHUTDOWN; cancelScheduleTask(); + if (reconnectTask != null) { + reconnectTask.cancel(); + reconnectTask = null; + } + reconnectPolicy = null; for (SubchannelData subchannelData : subchannels.values()) { subchannelData.getSubchannel().shutdown(); @@ -350,6 +400,12 @@ public void shutdown() { * that all other subchannels must be shutdown. */ private void shutdownRemaining(SubchannelData activeSubchannelData) { + if (reconnectTask != null) { + reconnectTask.cancel(); + reconnectTask = null; + } + reconnectPolicy = null; + cancelScheduleTask(); for (SubchannelData subchannelData : subchannels.values()) { if (!subchannelData.getSubchannel().equals(activeSubchannelData.subchannel)) { @@ -391,8 +447,17 @@ public void requestConnection() { scheduleNextConnection(); break; case TRANSIENT_FAILURE: - addressIndex.increment(); - requestConnection(); + if (!serializingRetries) { + addressIndex.increment(); + requestConnection(); + } else { + if (!addressIndex.isValid()) { + scheduleBackoff(); + } else { + subchannelData.subchannel.requestConnection(); + subchannelData.updateState(CONNECTING); + } + } break; default: // Wait for current subchannel to change state @@ -438,9 +503,10 @@ private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) HealthListener hcListener = new HealthListener(); final Subchannel subchannel = helper.createSubchannel( CreateSubchannelArgs.newBuilder() - .setAddresses(Lists.newArrayList( - new EquivalentAddressGroup(addr, attrs))) - .addOption(HEALTH_CONSUMER_LISTENER_ARG_KEY, hcListener) + .setAddresses(Lists.newArrayList( + new EquivalentAddressGroup(addr, attrs))) + .addOption(HEALTH_CONSUMER_LISTENER_ARG_KEY, hcListener) + .addOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY, serializingRetries) .build()); if (subchannel == null) { log.warning("Was not able to create subchannel for " + addr); @@ -458,7 +524,7 @@ private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) } private boolean isPassComplete() { - if (addressIndex.isValid() || subchannels.size() < addressIndex.size()) { + if (subchannels.size() < addressIndex.size()) { return false; } for (SubchannelData sc : subchannels.values()) { @@ -646,6 +712,16 @@ public int size() { } } + @VisibleForTesting + int getGroupIndex() { + return addressIndex.groupIndex; + } + + @VisibleForTesting + boolean isIndexValid() { + return addressIndex.isValid(); + } + private static final class SubchannelData { private final Subchannel subchannel; private ConnectivityState state; diff --git a/core/src/main/java/io/grpc/internal/SpiffeUtil.java b/core/src/main/java/io/grpc/internal/SpiffeUtil.java new file mode 100644 index 00000000000..bddce3d035e --- /dev/null +++ b/core/src/main/java/io/grpc/internal/SpiffeUtil.java @@ -0,0 +1,122 @@ +/* + * Copyright 2024 The gRPC Authors + * + * 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 io.grpc.internal; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Splitter; +import java.util.Locale; + +/** + * Helper utility to work with SPIFFE URIs. + * @see Standard + */ +public final class SpiffeUtil { + + private static final String PREFIX = "spiffe://"; + + private SpiffeUtil() {} + + /** + * Parses a URI string, applies validation rules described in SPIFFE standard, and, in case of + * success, returns parsed TrustDomain and Path. + * + * @param uri a String representing a SPIFFE ID + */ + public static SpiffeId parse(String uri) { + doInitialUriValidation(uri); + checkArgument(uri.toLowerCase(Locale.US).startsWith(PREFIX), "Spiffe Id must start with " + + PREFIX); + String domainAndPath = uri.substring(PREFIX.length()); + String trustDomain; + String path; + if (!domainAndPath.contains("/")) { + trustDomain = domainAndPath; + path = ""; + } else { + String[] parts = domainAndPath.split("/", 2); + trustDomain = parts[0]; + path = parts[1]; + checkArgument(!path.isEmpty(), "Path must not include a trailing '/'"); + } + validateTrustDomain(trustDomain); + validatePath(path); + if (!path.isEmpty()) { + path = "/" + path; + } + return new SpiffeId(trustDomain, path); + } + + private static void doInitialUriValidation(String uri) { + checkArgument(checkNotNull(uri, "uri").length() > 0, "Spiffe Id can't be empty"); + checkArgument(uri.length() <= 2048, "Spiffe Id maximum length is 2048 characters"); + checkArgument(!uri.contains("#"), "Spiffe Id must not contain query fragments"); + checkArgument(!uri.contains("?"), "Spiffe Id must not contain query parameters"); + } + + private static void validateTrustDomain(String trustDomain) { + checkArgument(!trustDomain.isEmpty(), "Trust Domain can't be empty"); + checkArgument(trustDomain.length() < 256, "Trust Domain maximum length is 255 characters"); + checkArgument(trustDomain.matches("[a-z0-9._-]+"), + "Trust Domain must contain only letters, numbers, dots, dashes, and underscores" + + " ([a-z0-9.-_])"); + } + + private static void validatePath(String path) { + if (path.isEmpty()) { + return; + } + checkArgument(!path.endsWith("/"), "Path must not include a trailing '/'"); + for (String segment : Splitter.on("/").split(path)) { + validatePathSegment(segment); + } + } + + private static void validatePathSegment(String pathSegment) { + checkArgument(!pathSegment.isEmpty(), "Individual path segments must not be empty"); + checkArgument(!(pathSegment.equals(".") || pathSegment.equals("..")), + "Individual path segments must not be relative path modifiers (i.e. ., ..)"); + checkArgument(pathSegment.matches("[a-zA-Z0-9._-]+"), + "Individual path segments must contain only letters, numbers, dots, dashes, and underscores" + + " ([a-zA-Z0-9.-_])"); + } + + /** + * Represents a SPIFFE ID as defined in the SPIFFE standard. + * @see Standard + */ + public static class SpiffeId { + + private final String trustDomain; + private final String path; + + private SpiffeId(String trustDomain, String path) { + this.trustDomain = trustDomain; + this.path = path; + } + + public String getTrustDomain() { + return trustDomain; + } + + public String getPath() { + return path; + } + } + +} diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java index 0512171f4e7..be304ad326b 100644 --- a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; @@ -152,12 +153,11 @@ public void close(Executor instance) {} private NameResolver.Listener2 mockListener; @Captor private ArgumentCaptor resultCaptor; - @Captor - private ArgumentCaptor errorCaptor; @Nullable private String networkaddressCacheTtlPropertyValue; @Mock private RecordFetcher recordFetcher; + @Mock private ProxyDetector mockProxyDetector; private RetryingNameResolver newResolver(String name, int defaultPort) { return newResolver( @@ -570,7 +570,7 @@ public List resolveAddress(String host) throws Exception { ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); verify(mockListener).onResult2(ac.capture()); verifyNoMoreInteractions(mockListener); - assertThat(ac.getValue().getAddresses()).isEmpty(); + assertThat(ac.getValue().getAddressesOrError().getValue()).isEmpty(); assertThat(ac.getValue().getServiceConfig()).isNull(); verify(mockResourceResolver, never()).resolveSrv(anyString()); @@ -578,6 +578,39 @@ public List resolveAddress(String host) throws Exception { assertEquals(0, fakeExecutor.numPendingTasks()); } + @Test + public void resolve_addressResolutionError() throws Exception { + DnsNameResolver.enableTxt = true; + when(mockProxyDetector.proxyFor(any(SocketAddress.class))).thenThrow(new IOException()); + RetryingNameResolver resolver = newResolver( + "addr.fake:1234", 443, mockProxyDetector, Stopwatch.createUnstarted()); + DnsNameResolver dnsResolver = (DnsNameResolver) resolver.getRetriedNameResolver(); + dnsResolver.setAddressResolver(new AddressResolver() { + @Override + public List resolveAddress(String host) throws Exception { + return Collections.emptyList(); + } + }); + ResourceResolver mockResourceResolver = mock(ResourceResolver.class); + when(mockResourceResolver.resolveTxt(anyString())) + .thenReturn(Collections.emptyList()); + + dnsResolver.setResourceResolver(mockResourceResolver); + + resolver.start(mockListener); + assertThat(fakeExecutor.runDueTasks()).isEqualTo(1); + + ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); + verify(mockListener).onResult2(ac.capture()); + verifyNoMoreInteractions(mockListener); + assertThat(ac.getValue().getAddressesOrError().getStatus().getCode()).isEqualTo( + Status.UNAVAILABLE.getCode()); + assertThat(ac.getValue().getAddressesOrError().getStatus().getDescription()).isEqualTo( + "Unable to resolve host addr.fake"); + assertThat(ac.getValue().getAddressesOrError().getStatus().getCause()) + .isInstanceOf(IOException.class); + } + // Load balancer rejects the empty addresses. @Test public void resolve_emptyResult_notAccepted() throws Exception { @@ -604,7 +637,7 @@ public List resolveAddress(String host) throws Exception { ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); verify(mockListener).onResult2(ac.capture()); verifyNoMoreInteractions(mockListener); - assertThat(ac.getValue().getAddresses()).isEmpty(); + assertThat(ac.getValue().getAddressesOrError().getValue()).isEmpty(); assertThat(ac.getValue().getServiceConfig()).isNull(); verify(mockResourceResolver, never()).resolveSrv(anyString()); @@ -632,7 +665,7 @@ public void resolve_nullResourceResolver() throws Exception { ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( - Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + Iterables.getOnlyElement(result.getAddressesOrError().getValue()).getAddresses()); assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); verify(mockAddressResolver).resolveAddress(name); assertThat(result.getServiceConfig()).isNull(); @@ -647,6 +680,7 @@ public void resolve_nullResourceResolver_addressFailure() throws Exception { AddressResolver mockAddressResolver = mock(AddressResolver.class); when(mockAddressResolver.resolveAddress(anyString())) .thenThrow(new IOException("no addr")); + when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.UNAVAILABLE); String name = "foo.googleapis.com"; RetryingNameResolver resolver = newResolver(name, 81); @@ -655,8 +689,8 @@ public void resolve_nullResourceResolver_addressFailure() throws Exception { dnsResolver.setResourceResolver(null); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onError(errorCaptor.capture()); - Status errorStatus = errorCaptor.getValue(); + verify(mockListener).onResult2(resultCaptor.capture()); + Status errorStatus = resultCaptor.getValue().getAddressesOrError().getStatus(); assertThat(errorStatus.getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(errorStatus.getCause()).hasMessageThat().contains("no addr"); @@ -704,7 +738,7 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) { ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( - Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + Iterables.getOnlyElement(result.getAddressesOrError().getValue()).getAddresses()); assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); assertThat(result.getServiceConfig().getConfig()).isNotNull(); verify(mockAddressResolver).resolveAddress(name); @@ -720,6 +754,7 @@ public void resolve_addressFailure_neverLookUpServiceConfig() throws Exception { AddressResolver mockAddressResolver = mock(AddressResolver.class); when(mockAddressResolver.resolveAddress(anyString())) .thenThrow(new IOException("no addr")); + when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.UNAVAILABLE); String name = "foo.googleapis.com"; ResourceResolver mockResourceResolver = mock(ResourceResolver.class); @@ -729,8 +764,8 @@ public void resolve_addressFailure_neverLookUpServiceConfig() throws Exception { dnsResolver.setResourceResolver(mockResourceResolver); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onError(errorCaptor.capture()); - Status errorStatus = errorCaptor.getValue(); + verify(mockListener).onResult2(resultCaptor.capture()); + Status errorStatus = resultCaptor.getValue().getAddressesOrError().getStatus(); assertThat(errorStatus.getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(errorStatus.getCause()).hasMessageThat().contains("no addr"); verify(mockResourceResolver, never()).resolveTxt(anyString()); @@ -762,7 +797,7 @@ public void resolve_serviceConfigLookupFails_nullServiceConfig() throws Exceptio ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( - Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + Iterables.getOnlyElement(result.getAddressesOrError().getValue()).getAddresses()); assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); verify(mockAddressResolver).resolveAddress(name); assertThat(result.getServiceConfig()).isNull(); @@ -794,7 +829,7 @@ public void resolve_serviceConfigMalformed_serviceConfigError() throws Exception ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( - Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + Iterables.getOnlyElement(result.getAddressesOrError().getValue()).getAddresses()); assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); verify(mockAddressResolver).resolveAddress(name); assertThat(result.getServiceConfig()).isNotNull(); @@ -859,7 +894,7 @@ public HttpConnectProxiedSocketAddress proxyFor(SocketAddress targetAddress) { assertEquals(1, fakeExecutor.runDueTasks()); verify(mockListener).onResult2(resultCaptor.capture()); - List result = resultCaptor.getValue().getAddresses(); + List result = resultCaptor.getValue().getAddressesOrError().getValue(); assertThat(result).hasSize(1); EquivalentAddressGroup eag = result.get(0); assertThat(eag.getAddresses()).hasSize(1); @@ -1299,9 +1334,9 @@ private List createAddressList(int n) throws UnknownHostException { private static void assertAnswerMatches( List addrs, int port, ResolutionResult resolutionResult) { - assertThat(resolutionResult.getAddresses()).hasSize(addrs.size()); + assertThat(resolutionResult.getAddressesOrError().getValue()).hasSize(addrs.size()); for (int i = 0; i < addrs.size(); i++) { - EquivalentAddressGroup addrGroup = resolutionResult.getAddresses().get(i); + EquivalentAddressGroup addrGroup = resolutionResult.getAddressesOrError().getValue().get(i); InetSocketAddress socketAddr = (InetSocketAddress) Iterables.getOnlyElement(addrGroup.getAddresses()); assertEquals("Addr " + i, port, socketAddr.getPort()); diff --git a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java index e4d9f27ed46..b75fd43a743 100644 --- a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java +++ b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java @@ -46,6 +46,7 @@ import io.grpc.InternalChannelz; import io.grpc.InternalLogId; import io.grpc.InternalWithLogId; +import io.grpc.LoadBalancer; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.internal.InternalSubchannel.CallTracingTransport; @@ -309,10 +310,57 @@ public void constructor_eagListWithNull_throws() { verify(mockBackoffPolicy2, times(backoff2Consulted)).nextBackoffNanos(); } + @Test public void twoAddressesReconnectDisabled() { + SocketAddress addr1 = mock(SocketAddress.class); + SocketAddress addr2 = mock(SocketAddress.class); + createInternalSubchannel(true, + new EquivalentAddressGroup(Arrays.asList(addr1, addr2))); + assertEquals(IDLE, internalSubchannel.getState()); + + assertNull(internalSubchannel.obtainActiveTransport()); + assertExactCallbackInvokes("onStateChange:CONNECTING"); + assertEquals(CONNECTING, internalSubchannel.getState()); + verify(mockTransportFactory).newClientTransport(eq(addr1), any(), any()); + // Let this one fail without success + transports.poll().listener.transportShutdown(Status.UNAVAILABLE); + // Still in CONNECTING + assertNull(internalSubchannel.obtainActiveTransport()); + assertNoCallbackInvoke(); + assertEquals(CONNECTING, internalSubchannel.getState()); + + // Second attempt will start immediately. Still no back-off policy. + verify(mockBackoffPolicyProvider, times(0)).get(); + verify(mockTransportFactory, times(1)) + .newClientTransport( + eq(addr2), + eq(createClientTransportOptions()), + isA(TransportLogger.class)); + assertNull(internalSubchannel.obtainActiveTransport()); + // Fail this one too + assertNoCallbackInvoke(); + transports.poll().listener.transportShutdown(Status.UNAVAILABLE); + // All addresses have failed, but we aren't controlling retries. + assertEquals(IDLE, internalSubchannel.getState()); + assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE); + // Backoff reset and first back-off interval begins + verify(mockBackoffPolicy1, never()).nextBackoffNanos(); + verify(mockBackoffPolicyProvider, never()).get(); + assertTrue("Nothing should have been scheduled", fakeClock.getPendingTasks().isEmpty()); + + // Should follow orders and create an active transport. + internalSubchannel.obtainActiveTransport(); + assertExactCallbackInvokes("onStateChange:CONNECTING"); + assertEquals(CONNECTING, internalSubchannel.getState()); + + // Shouldn't have anything scheduled, so shouldn't do anything + assertTrue("Nothing should have been scheduled 2", fakeClock.getPendingTasks().isEmpty()); + } + @Test public void twoAddressesReconnect() { SocketAddress addr1 = mock(SocketAddress.class); SocketAddress addr2 = mock(SocketAddress.class); - createInternalSubchannel(addr1, addr2); + createInternalSubchannel(false, + new EquivalentAddressGroup(Arrays.asList(addr1, addr2))); assertEquals(IDLE, internalSubchannel.getState()); // Invocation counters int transportsAddr1 = 0; @@ -1377,11 +1425,24 @@ private void createInternalSubchannel(SocketAddress ... addrs) { } private void createInternalSubchannel(EquivalentAddressGroup ... addrs) { + createInternalSubchannel(false, addrs); + } + + private void createInternalSubchannel(boolean reconnectDisabled, + EquivalentAddressGroup ... addrs) { List addressGroups = Arrays.asList(addrs); InternalLogId logId = InternalLogId.allocate("Subchannel", /*details=*/ AUTHORITY); ChannelTracer subchannelTracer = new ChannelTracer(logId, 10, fakeClock.getTimeProvider().currentTimeNanos(), "Subchannel"); - internalSubchannel = new InternalSubchannel(addressGroups, AUTHORITY, USER_AGENT, + LoadBalancer.CreateSubchannelArgs.Builder argBuilder = + LoadBalancer.CreateSubchannelArgs.newBuilder().setAddresses(addressGroups); + if (reconnectDisabled) { + argBuilder.addOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY, reconnectDisabled); + } + LoadBalancer.CreateSubchannelArgs createSubchannelArgs = argBuilder.build(); + internalSubchannel = new InternalSubchannel( + createSubchannelArgs, + AUTHORITY, USER_AGENT, mockBackoffPolicyProvider, mockTransportFactory, fakeClock.getScheduledExecutorService(), fakeClock.getStopwatchSupplier(), syncContext, mockInternalSubchannelCallback, channelz, CallTracer.getDefaultFactory().create(), diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java index 90008c1be30..293d0e70961 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java @@ -63,6 +63,7 @@ import io.grpc.NameResolver.ResolutionResult; import io.grpc.NameResolverProvider; import io.grpc.Status; +import io.grpc.StatusOr; import io.grpc.StringMarshaller; import io.grpc.internal.FakeClock.ScheduledTask; import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder; @@ -615,7 +616,7 @@ private void deliverResolutionResult() { // the NameResolver. ResolutionResult resolutionResult = ResolutionResult.newBuilder() - .setAddresses(servers) + .setAddressesOrError(StatusOr.fromValue(servers)) .setAttributes(Attributes.EMPTY) .build(); nameResolverListenerCaptor.getValue().onResult(resolutionResult); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 4d42056b689..16700096827 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -84,6 +84,7 @@ import io.grpc.InternalChannelz; import io.grpc.InternalChannelz.ChannelStats; import io.grpc.InternalChannelz.ChannelTrace; +import io.grpc.InternalChannelz.ChannelTrace.Event.Severity; import io.grpc.InternalConfigSelector; import io.grpc.InternalInstrumented; import io.grpc.LoadBalancer; @@ -115,6 +116,7 @@ import io.grpc.ServerMethodDefinition; import io.grpc.Status; import io.grpc.Status.Code; +import io.grpc.StatusOr; import io.grpc.StringMarshaller; import io.grpc.SynchronizationContext; import io.grpc.internal.ClientTransportFactory.ClientTransportOptions; @@ -123,6 +125,7 @@ import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder; +import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo; import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.internal.TestUtils.MockClientTransportInfo; import io.grpc.stub.ClientCalls; @@ -1054,7 +1057,7 @@ public void noMoreCallbackAfterLoadBalancerShutdown() { verifyNoMoreInteractions(mockLoadBalancer); } - @Test + @Test public void noMoreCallbackAfterLoadBalancerShutdown_configError() throws InterruptedException { FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri) @@ -1093,7 +1096,10 @@ public void noMoreCallbackAfterLoadBalancerShutdown_configError() throws Interru verify(stateListener2).onSubchannelState(stateInfoCaptor.capture()); assertSame(CONNECTING, stateInfoCaptor.getValue().getState()); - resolver.listener.onError(resolutionError); + channel.syncContext.execute(() -> + resolver.listener.onResult2( + ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromStatus(resolutionError)).build())); verify(mockLoadBalancer).handleNameResolutionError(resolutionError); verifyNoMoreInteractions(mockLoadBalancer); @@ -1115,18 +1121,64 @@ public void noMoreCallbackAfterLoadBalancerShutdown_configError() throws Interru verifyNoMoreInteractions(stateListener1, stateListener2); // No more callback should be delivered to LoadBalancer after it's shut down - resolver.listener.onResult( - ResolutionResult.newBuilder() - .setAddresses(new ArrayList<>()) - .setServiceConfig( - ConfigOrError.fromError(Status.UNAVAILABLE.withDescription("Resolution failed"))) - .build()); - Thread.sleep(1100); + channel.syncContext.execute(() -> + resolver.listener.onResult2( + ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromStatus(resolutionError)).build())); assertThat(timer.getPendingTasks()).isEmpty(); resolver.resolved(); verifyNoMoreInteractions(mockLoadBalancer); } + @Test + public void addressResolutionError_noPriorNameResolution_usesDefaultServiceConfig() + throws Exception { + Map rawServiceConfig = + parseConfig("{\"methodConfig\":[{" + + "\"name\":[{\"service\":\"service\"}]," + + "\"waitForReady\":true}]}"); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + FakeNameResolverFactory nameResolverFactory = + new FakeNameResolverFactory.Builder(expectedUri) + .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) + .setResolvedAtStart(false) + .build(); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); + channelBuilder.nameResolverFactory(nameResolverFactory); + Map defaultServiceConfig = + parseConfig("{\"methodConfig\":[{" + + "\"name\":[{\"service\":\"service\"}]," + + "\"waitForReady\":true}]}"); + channelBuilder.defaultServiceConfig(defaultServiceConfig); + Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed"); + channelBuilder.maxTraceEvents(10); + createChannel(); + FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); + + resolver.listener.onError(resolutionError); + + InternalConfigSelector configSelector = channel.getConfigSelector(); + ManagedChannelServiceConfig config = + (ManagedChannelServiceConfig) configSelector.selectConfig(null).getConfig(); + MethodInfo methodConfig = config.getMethodConfig(method); + assertThat(methodConfig.waitForReady).isTrue(); + timer.forwardNanos(1234); + assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() + .setDescription("Initial Name Resolution error, using default service config") + .setSeverity(Severity.CT_ERROR) + .setTimestampNanos(0) + .build()); + + // Check that "lastServiceConfig" variable has been set above: a config resolution with the same + // config simply gets ignored and not gets reassigned. + resolver.resolved(); + timer.forwardNanos(1234); + assertThat(getStats(channel).channelTrace.events.stream().filter( + event -> event.description.equals("Service config changed")).count()).isEqualTo(0); + } + @Test public void interceptor() throws Exception { final AtomicLong atomic = new AtomicLong(); @@ -3235,11 +3287,19 @@ public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends_usesListener assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); prevSize = getStats(channel).channelTrace.events.size(); - nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); + channel.syncContext.execute(() -> + nameResolverFactory.resolvers.get(0).listener.onResult2( + ResolutionResult.newBuilder() + .setAddressesOrError( + StatusOr.fromStatus(Status.INTERNAL)).build())); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); prevSize = getStats(channel).channelTrace.events.size(); - nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); + channel.syncContext.execute(() -> + nameResolverFactory.resolvers.get(0).listener.onResult2( + ResolutionResult.newBuilder() + .setAddressesOrError( + StatusOr.fromStatus(Status.INTERNAL)).build())); assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); prevSize = getStats(channel).channelTrace.events.size(); @@ -4595,7 +4655,7 @@ public void notUseDefaultImmediatelyIfEnableLookUp() throws Exception { int size = getStats(channel).channelTrace.events.size(); assertThat(getStats(channel).channelTrace.events.get(size - 1)) .isNotEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Using default service config") + .setDescription("timer.forwardNanos(1234);") .setSeverity(ChannelTrace.Event.Severity.CT_INFO) .setTimestampNanos(timer.getTicker().read()) .build()); @@ -4868,7 +4928,10 @@ final class FakeNameResolver extends NameResolver { void resolved() { if (error != null) { - listener.onError(error); + syncContext.execute(() -> + listener.onResult2( + ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromStatus(error)).build())); return; } ResolutionResult.Builder builder = diff --git a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java index 1ec1ccb2082..8f1b908e999 100644 --- a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java +++ b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java @@ -133,7 +133,7 @@ public void simplePayload() { assertEquals(Bytes.asList(new byte[]{3, 14}), bytes(producer.getValue().next())); verify(listener, atLeastOnce()).bytesRead(anyInt()); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock, 2, 2); + checkStats(tracer, transportTracer.getStats(), fakeClock, useGzipInflatingBuffer, 2, 2); } @Test @@ -148,7 +148,7 @@ public void smallCombinedPayloads() { verify(listener, atLeastOnce()).bytesRead(anyInt()); assertEquals(Bytes.asList(new byte[]{14, 15}), bytes(streams.get(1).next())); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock, 1, 1, 2, 2); + checkStats(tracer, transportTracer.getStats(), fakeClock, useGzipInflatingBuffer, 1, 1, 2, 2); } @Test @@ -162,7 +162,7 @@ public void endOfStreamWithPayloadShouldNotifyEndOfStream() { verify(listener).deframerClosed(false); verify(listener, atLeastOnce()).bytesRead(anyInt()); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock, 1, 1); + checkStats(tracer, transportTracer.getStats(), fakeClock, useGzipInflatingBuffer, 1, 1); } @Test @@ -177,7 +177,7 @@ public void endOfStreamShouldNotifyEndOfStream() { } verify(listener).deframerClosed(false); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock); + checkStats(tracer, transportTracer.getStats(), fakeClock, false); } @Test @@ -189,7 +189,7 @@ public void endOfStreamWithPartialMessageShouldNotifyDeframerClosedWithPartialMe verify(listener, atLeastOnce()).bytesRead(anyInt()); verify(listener).deframerClosed(true); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock); + checkStats(tracer, transportTracer.getStats(), fakeClock, false); } @Test @@ -206,7 +206,7 @@ public void endOfStreamWithInvalidGzipBlockShouldNotifyDeframerClosedWithPartial deframer.closeWhenComplete(); verify(listener).deframerClosed(true); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock); + checkStats(tracer, transportTracer.getStats(), fakeClock, false); } @Test @@ -228,10 +228,11 @@ public void payloadSplitBetweenBuffers() { tracer, transportTracer.getStats(), fakeClock, + true, 7 /* msg size */ + 2 /* second buffer adds two bytes of overhead in deflate block */, 7); } else { - checkStats(tracer, transportTracer.getStats(), fakeClock, 7, 7); + checkStats(tracer, transportTracer.getStats(), fakeClock, false, 7, 7); } } @@ -248,7 +249,7 @@ public void frameHeaderSplitBetweenBuffers() { assertEquals(Bytes.asList(new byte[]{3}), bytes(producer.getValue().next())); verify(listener, atLeastOnce()).bytesRead(anyInt()); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock, 1, 1); + checkStats(tracer, transportTracer.getStats(), fakeClock, useGzipInflatingBuffer, 1, 1); } @Test @@ -259,7 +260,7 @@ public void emptyPayload() { assertEquals(Bytes.asList(), bytes(producer.getValue().next())); verify(listener, atLeastOnce()).bytesRead(anyInt()); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock, 0, 0); + checkStats(tracer, transportTracer.getStats(), fakeClock, useGzipInflatingBuffer, 0, 0); } @Test @@ -273,9 +274,10 @@ public void largerFrameSize() { verify(listener, atLeastOnce()).bytesRead(anyInt()); verifyNoMoreInteractions(listener); if (useGzipInflatingBuffer) { - checkStats(tracer, transportTracer.getStats(), fakeClock, 8 /* compressed size */, 1000); + checkStats(tracer, transportTracer.getStats(), fakeClock,true, + 8 /* compressed size */, 1000); } else { - checkStats(tracer, transportTracer.getStats(), fakeClock, 1000, 1000); + checkStats(tracer, transportTracer.getStats(), fakeClock, false, 1000, 1000); } } @@ -292,7 +294,7 @@ public void endOfStreamCallbackShouldWaitForMessageDelivery() { verify(listener).deframerClosed(false); verify(listener, atLeastOnce()).bytesRead(anyInt()); verifyNoMoreInteractions(listener); - checkStats(tracer, transportTracer.getStats(), fakeClock, 1, 1); + checkStats(tracer, transportTracer.getStats(), fakeClock, useGzipInflatingBuffer, 1, 1); } @Test @@ -308,6 +310,7 @@ public void compressed() { verify(listener).messagesAvailable(producer.capture()); assertEquals(Bytes.asList(new byte[1000]), bytes(producer.getValue().next())); verify(listener, atLeastOnce()).bytesRead(anyInt()); + checkStats(tracer, transportTracer.getStats(), fakeClock, true, 29, 1000); verifyNoMoreInteractions(listener); } @@ -502,7 +505,8 @@ public void sizeEnforcingInputStream_markReset() throws IOException { * @param sizes in the format {wire0, uncompressed0, wire1, uncompressed1, ...} */ private static void checkStats( - TestBaseStreamTracer tracer, TransportStats transportStats, FakeClock clock, long... sizes) { + TestBaseStreamTracer tracer, TransportStats transportStats, FakeClock clock, + boolean compressed, long... sizes) { assertEquals(0, sizes.length % 2); int count = sizes.length / 2; long expectedWireSize = 0; @@ -510,7 +514,8 @@ private static void checkStats( for (int i = 0; i < count; i++) { assertEquals("inboundMessage(" + i + ")", tracer.nextInboundEvent()); assertEquals( - String.format(Locale.US, "inboundMessageRead(%d, %d, -1)", i, sizes[i * 2]), + String.format(Locale.US, "inboundMessageRead(%d, %d, %d)", i, sizes[i * 2], + compressed ? -1 : sizes[i * 2 + 1]), tracer.nextInboundEvent()); expectedWireSize += sizes[i * 2]; expectedUncompressedSize += sizes[i * 2 + 1]; diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index 63915bddc99..f0031a6ae62 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -27,10 +27,12 @@ import static io.grpc.LoadBalancer.HEALTH_CONSUMER_LISTENER_ARG_KEY; import static io.grpc.LoadBalancer.IS_PETIOLE_POLICY; import static io.grpc.internal.PickFirstLeafLoadBalancer.CONNECTION_DELAY_INTERVAL_MS; +import static io.grpc.internal.PickFirstLeafLoadBalancer.isSerializingRetries; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assume.assumeTrue; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -73,7 +75,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.junit.After; -import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -92,14 +93,22 @@ public class PickFirstLeafLoadBalancerTest { public static final Status CONNECTION_ERROR = Status.UNAVAILABLE.withDescription("Simulated connection error"); - - @Parameterized.Parameters(name = "{0}") - public static List enableHappyEyeballs() { - return Arrays.asList(true, false); + public static final String GRPC_SERIALIZE_RETRIES = "GRPC_SERIALIZE_RETRIES"; + + @Parameterized.Parameters(name = "{0}-{1}") + public static List data() { + return Arrays.asList(new Object[][] { + {false, false}, + {false, true}, + {true, false}}); } - @Parameterized.Parameter + @Parameterized.Parameter(value = 0) + public boolean serializeRetries; + + @Parameterized.Parameter(value = 1) public boolean enableHappyEyeballs; + private PickFirstLeafLoadBalancer loadBalancer; private final List servers = Lists.newArrayList(); private static final Attributes.Key FOO = Attributes.Key.create("foo"); @@ -137,13 +146,22 @@ public void uncaughtException(Thread t, Throwable e) { private PickSubchannelArgs mockArgs; private String originalHappyEyeballsEnabledValue; + private String originalSerializeRetriesValue; + + private long backoffMillis; @Before public void setUp() { + assumeTrue(!serializeRetries || !enableHappyEyeballs); // they are not compatible + + backoffMillis = TimeUnit.SECONDS.toMillis(1); + originalSerializeRetriesValue = System.getProperty(GRPC_SERIALIZE_RETRIES); + System.setProperty(GRPC_SERIALIZE_RETRIES, Boolean.toString(serializeRetries)); + originalHappyEyeballsEnabledValue = System.getProperty(PickFirstLoadBalancerProvider.GRPC_PF_USE_HAPPY_EYEBALLS); System.setProperty(PickFirstLoadBalancerProvider.GRPC_PF_USE_HAPPY_EYEBALLS, - enableHappyEyeballs ? "true" : "false"); + Boolean.toString(enableHappyEyeballs)); for (int i = 1; i <= 5; i++) { SocketAddress addr = new FakeSocketAddress("server" + i); @@ -176,6 +194,11 @@ public void setUp() { @After public void tearDown() { + if (originalSerializeRetriesValue == null) { + System.clearProperty(GRPC_SERIALIZE_RETRIES); + } else { + System.setProperty(GRPC_SERIALIZE_RETRIES, originalSerializeRetriesValue); + } if (originalHappyEyeballsEnabledValue == null) { System.clearProperty(PickFirstLoadBalancerProvider.GRPC_PF_USE_HAPPY_EYEBALLS); } else { @@ -498,6 +521,9 @@ public void healthCheckFlow() { inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); assertThat(pickerCaptor.getValue().pickSubchannel(mockArgs) .getSubchannel()).isSameInstanceAs(mockSubchannel1); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); + verifyNoMoreInteractions(mockHelper); healthListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); verifyNoMoreInteractions(mockHelper); @@ -520,20 +546,7 @@ public void pickAfterStateChangeAfterResolution() { inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); stateListeners[0] = stateListenerCaptor.getValue(); - if (enableHappyEyeballs) { - forwardTimeByConnectionDelay(); - inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture()); - stateListeners[1] = stateListenerCaptor.getValue(); - forwardTimeByConnectionDelay(); - inOrder.verify(mockSubchannel3).start(stateListenerCaptor.capture()); - stateListeners[2] = stateListenerCaptor.getValue(); - forwardTimeByConnectionDelay(); - inOrder.verify(mockSubchannel4).start(stateListenerCaptor.capture()); - stateListeners[3] = stateListenerCaptor.getValue(); - } - - reset(mockHelper); - + stateListeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(READY)); stateListeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(IDLE)); inOrder.verify(mockHelper).refreshNameResolution(); inOrder.verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture()); @@ -543,11 +556,23 @@ public void pickAfterStateChangeAfterResolution() { stateListeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); Status error = Status.UNAVAILABLE.withDescription("boom!"); + reset(mockHelper); if (enableHappyEyeballs) { - for (SubchannelStateListener listener : stateListeners) { - listener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); - } + stateListeners[0].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); + inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture()); + stateListeners[1] = stateListenerCaptor.getValue(); + stateListeners[1].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); + inOrder.verify(mockSubchannel3).start(stateListenerCaptor.capture()); + stateListeners[2] = stateListenerCaptor.getValue(); + stateListeners[2].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); + inOrder.verify(mockSubchannel4).start(stateListenerCaptor.capture()); + stateListeners[3] = stateListenerCaptor.getValue(); + stateListeners[3].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); } else { stateListeners[0].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); for (int i = 1; i < stateListeners.length; i++) { @@ -589,6 +614,8 @@ public void pickAfterResolutionAfterTransientValue() { // Transition from TRANSIENT_ERROR to CONNECTING should also be ignored. stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); verifyNoMoreInteractions(mockHelper); assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); } @@ -619,6 +646,8 @@ public void pickWithDupAddressesUpDownUp() { // Transition from TRANSIENT_ERROR to CONNECTING should also be ignored. stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); verifyNoMoreInteractions(mockHelper); assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); @@ -651,6 +680,8 @@ public void pickWithDupEagsUpDownUp() { // Transition from TRANSIENT_ERROR to CONNECTING should also be ignored. stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); verifyNoMoreInteractions(mockHelper); assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); @@ -1518,6 +1549,8 @@ public void updateAddresses_intersecting_ready() { @Test public void updateAddresses_intersecting_transient_failure() { + assumeTrue(!isSerializingRetries()); + // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4); // captor: captures @@ -1782,6 +1815,8 @@ public void updateAddresses_identical_ready() { @Test public void updateAddresses_identical_transient_failure() { + assumeTrue(!isSerializingRetries()); + InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4); // Creating first set of endpoints/addresses @@ -2295,7 +2330,7 @@ public void ready_then_transient_failure_again() { @Test public void happy_eyeballs_trigger_connection_delay() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4); @@ -2340,7 +2375,7 @@ public void happy_eyeballs_trigger_connection_delay() { @Test public void happy_eyeballs_connection_results_happen_after_get_to_end() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); Status error = Status.UNAUTHENTICATED.withDescription("simulated failure"); @@ -2393,7 +2428,7 @@ public void happy_eyeballs_connection_results_happen_after_get_to_end() { @Test public void happy_eyeballs_pick_pushes_index_over_end() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel2n2, mockSubchannel3n2); @@ -2471,7 +2506,7 @@ public void happy_eyeballs_pick_pushes_index_over_end() { @Test public void happy_eyeballs_fail_then_trigger_connection_delay() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); assertEquals(IDLE, loadBalancer.getConcludedConnectivityState()); @@ -2550,6 +2585,44 @@ public void advance_index_then_request_connection() { loadBalancer.requestConnection(); // should be handled without throwing exception } + @Test + public void serialized_retries_two_passes() { + assumeTrue(serializeRetries); // This test is only for serialized retries + + InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); + Status error = Status.UNAUTHENTICATED.withDescription("simulated failure"); + + List addrs = + Lists.newArrayList(servers.get(0), servers.get(1), servers.get(2)); + Subchannel[] subchannels = new Subchannel[]{mockSubchannel1, mockSubchannel2, mockSubchannel3}; + SubchannelStateListener[] listeners = new SubchannelStateListener[subchannels.length]; + loadBalancer.acceptResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(addrs).build()); + forwardTimeByConnectionDelay(2); + for (int i = 0; i < subchannels.length; i++) { + inOrder.verify(subchannels[i]).start(stateListenerCaptor.capture()); + inOrder.verify(subchannels[i]).requestConnection(); + listeners[i] = stateListenerCaptor.getValue(); + listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + } + assertEquals(TRANSIENT_FAILURE, loadBalancer.getConcludedConnectivityState()); + assertFalse("Index should be at end", loadBalancer.isIndexValid()); + + forwardTimeByBackoffDelay(); // should trigger retry + for (int i = 0; i < subchannels.length; i++) { + inOrder.verify(subchannels[i]).requestConnection(); + listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); // cascade + } + inOrder.verify(subchannels[0], never()).requestConnection(); // should wait for backoff delay + + forwardTimeByBackoffDelay(); // should trigger retry again + for (int i = 0; i < subchannels.length; i++) { + inOrder.verify(subchannels[i]).requestConnection(); + assertEquals(i, loadBalancer.getGroupIndex()); + listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); // cascade + } + } + @Test public void index_looping() { Attributes.Key key = Attributes.Key.create("some-key"); @@ -2689,6 +2762,11 @@ private void forwardTimeByConnectionDelay(int times) { } } + private void forwardTimeByBackoffDelay() { + backoffMillis = (long) (backoffMillis * 1.8); // backoff factor default is 1.6 with Jitter .2 + fakeClock.forwardTime(backoffMillis, TimeUnit.MILLISECONDS); + } + private void acceptXSubchannels(int num) { List newServers = new ArrayList<>(); for (int i = 0; i < num; i++) { diff --git a/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java b/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java new file mode 100644 index 00000000000..c3a98ce33e0 --- /dev/null +++ b/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2024 The gRPC Authors + * + * 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 io.grpc.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Arrays; +import java.util.Collection; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + + +@RunWith(Enclosed.class) +public class SpiffeUtilTest { + + @RunWith(Parameterized.class) + public static class ParseSuccessTest { + @Parameter + public String uri; + + @Parameter(1) + public String trustDomain; + + @Parameter(2) + public String path; + + @Test + public void parseSuccessTest() { + SpiffeUtil.SpiffeId spiffeId = SpiffeUtil.parse(uri); + assertEquals(trustDomain, spiffeId.getTrustDomain()); + assertEquals(path, spiffeId.getPath()); + } + + @Parameters(name = "spiffeId={0}") + public static Collection data() { + return Arrays.asList(new String[][] { + {"spiffe://example.com", "example.com", ""}, + {"spiffe://example.com/us", "example.com", "/us"}, + {"spIFfe://qa-staging.final_check.example.com/us", "qa-staging.final_check.example.com", + "/us"}, + {"spiffe://example.com/country/us/state/FL/city/Miami", "example.com", + "/country/us/state/FL/city/Miami"}, + {"SPIFFE://example.com/Czech.Republic/region0.1/city_of-Prague", "example.com", + "/Czech.Republic/region0.1/city_of-Prague"}, + {"spiffe://trust-domain-name/path", "trust-domain-name", "/path"}, + {"spiffe://staging.example.com/payments/mysql", "staging.example.com", "/payments/mysql"}, + {"spiffe://staging.example.com/payments/web-fe", "staging.example.com", + "/payments/web-fe"}, + {"spiffe://k8s-west.example.com/ns/staging/sa/default", "k8s-west.example.com", + "/ns/staging/sa/default"}, + {"spiffe://example.com/9eebccd2-12bf-40a6-b262-65fe0487d453", "example.com", + "/9eebccd2-12bf-40a6-b262-65fe0487d453"}, + {"spiffe://trustdomain/.a..", "trustdomain", "/.a.."}, + {"spiffe://trustdomain/...", "trustdomain", "/..."}, + {"spiffe://trustdomain/abcdefghijklmnopqrstuvwxyz", "trustdomain", + "/abcdefghijklmnopqrstuvwxyz"}, + {"spiffe://trustdomain/abc0123.-_", "trustdomain", "/abc0123.-_"}, + {"spiffe://trustdomain/0123456789", "trustdomain", "/0123456789"}, + {"spiffe://trustdomain0123456789/path", "trustdomain0123456789", "/path"}, + }); + } + } + + @RunWith(Parameterized.class) + public static class ParseFailureTest { + @Parameter + public String uri; + + @Test + public void parseFailureTest() { + assertThrows(IllegalArgumentException.class, () -> SpiffeUtil.parse(uri)); + } + + @Parameters(name = "spiffeId={0}") + public static Collection data() { + return Arrays.asList( + "spiffe:///", + "spiffe://example!com", + "spiffe://exampleя.com/workload-1", + "spiffe://example.com/us/florida/miamiя", + "spiffe:/trustdomain/path", + "spiffe:///path", + "spiffe://trust%20domain/path", + "spiffe://user@trustdomain/path", + "spiffe:// /", + "", + "http://trustdomain/path", + "//trustdomain/path", + "://trustdomain/path", + "piffe://trustdomain/path", + "://", + "://trustdomain", + "spiff", + "spiffe", + "spiffe:////", + "spiffe://trust.domain/../path" + ); + } + } + + public static class ExceptionMessageTest { + + @Test + public void spiffeUriFormatTest() { + NullPointerException npe = assertThrows(NullPointerException.class, () -> + SpiffeUtil.parse(null)); + assertEquals("uri", npe.getMessage()); + + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("https://example.com")); + assertEquals("Spiffe Id must start with spiffe://", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/workload#1")); + assertEquals("Spiffe Id must not contain query fragments", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/workload-1?t=1")); + assertEquals("Spiffe Id must not contain query parameters", iae.getMessage()); + } + + @Test + public void spiffeTrustDomainFormatTest() { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://")); + assertEquals("Trust Domain can't be empty", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://eXample.com")); + assertEquals( + "Trust Domain must contain only letters, numbers, dots, dashes, and underscores " + + "([a-z0-9.-_])", + iae.getMessage()); + + StringBuilder longTrustDomain = new StringBuilder("spiffe://pi.eu."); + for (int i = 0; i < 50; i++) { + longTrustDomain.append("pi.eu"); + } + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse(longTrustDomain.toString())); + assertEquals("Trust Domain maximum length is 255 characters", iae.getMessage()); + + StringBuilder longSpiffe = new StringBuilder(String.format("spiffe://mydomain%scom/", "%21")); + for (int i = 0; i < 405; i++) { + longSpiffe.append("qwert"); + } + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse(longSpiffe.toString())); + assertEquals("Spiffe Id maximum length is 2048 characters", iae.getMessage()); + } + + @Test + public void spiffePathFormatTest() { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com//")); + assertEquals("Path must not include a trailing '/'", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/")); + assertEquals("Path must not include a trailing '/'", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/us//miami")); + assertEquals("Individual path segments must not be empty", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/us/.")); + assertEquals("Individual path segments must not be relative path modifiers (i.e. ., ..)", + iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/us!")); + assertEquals("Individual path segments must contain only letters, numbers, dots, dashes, and " + + "underscores ([a-zA-Z0-9.-_])", iae.getMessage()); + } + } +} \ No newline at end of file diff --git a/documentation/android-binderchannel-status-codes.md b/documentation/android-binderchannel-status-codes.md index dda0220bf8a..28bdd8907c1 100644 --- a/documentation/android-binderchannel-status-codes.md +++ b/documentation/android-binderchannel-status-codes.md @@ -23,15 +23,23 @@ Consider the table that follows as an BinderChannel-specific addendum to the “ - 1 + 0 - Server app not installed + Server app not visible. + + bindService() returns false + +

UNIMPLEMENTED

“The operation is not implemented or is not supported / enabled in this service.” + + Give up - This is an error in the client manifest. - bindService() returns false + + + 1 -

UNIMPLEMENTED

“The operation is not implemented or is not supported / enabled in this service.” + Server app not installed - Direct the user to install/reinstall the server app. + Direct the user to install/reinstall the server app. @@ -90,6 +98,8 @@ Consider the table that follows as an BinderChannel-specific addendum to the “

PERMISSION_DENIED

“The caller does not have permission to execute the specified operation …” + Direct the user to update the server app in the hopes that a newer version fixes this error in its manifest. + 10 @@ -315,6 +325,7 @@ According to a review of the AOSP source code, there are in fact several cases: 1. The target package is not installed 2. The target package is installed but does not declare the target Service in its manifest. 3. The target package requests dangerous permissions but targets sdk <= M and therefore requires a permissions review, but the caller is not running in the foreground and so it would be inappropriate to launch the review UI. +4. The target package is not visible to the client due to [Android 11 package visibility rules](https://developer.android.com/training/package-visibility). Status code mapping: **UNIMPLEMENTED** @@ -322,6 +333,7 @@ Status code mapping: **UNIMPLEMENTED** Unfortunately `UNIMPLEMENTED` doesn’t capture (3) but none of the other canonical status codes do either and we expect this case to be extremely rare. +(4) is intentially indistinguishable from (1) by Android design so we can't handle it differently. However, as a client manifest error, it's not something reasonable apps would handle at runtime anyway. ### bindService() throws SecurityException @@ -382,4 +394,4 @@ Android’s Parcel class exposes a mechanism for marshalling certain types of `R The calling Activity or Service Context might be destroyed with a gRPC request in flight. Apps should cease operations when the Context hosting it goes away and this includes cancelling any outstanding RPCs. -Status code mapping: **CANCELLED** \ No newline at end of file +Status code mapping: **CANCELLED** diff --git a/examples/build.gradle b/examples/build.gradle index c10b4eef46a..24f6d2b04f8 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 0d7d959de93..6b1f0ded169 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { // grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index 5565747cb19..97bca5f91b6 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 064d989c04c..dc399a78c9d 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle index 554b5f758d9..0c45e6de7cf 100644 --- a/examples/example-dualstack/build.gradle +++ b/examples/example-dualstack/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml index dfd650cdfa4..a8e32b347e5 100644 --- a/examples/example-dualstack/pom.xml +++ b/examples/example-dualstack/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-gauth/README.md b/examples/example-gauth/README.md index 622c14cb57b..b49d346a9be 100644 --- a/examples/example-gauth/README.md +++ b/examples/example-gauth/README.md @@ -43,13 +43,13 @@ gcloud pubsub topics create Topic1 5. You will now need to set up [authentication](https://cloud.google.com/docs/authentication/) and a [service account](https://cloud.google.com/docs/authentication/#service_accounts) in order to access Pub/Sub via gRPC APIs as described [here](https://cloud.google.com/iam/docs/creating-managing-service-accounts). -Assign the [role](https://cloud.google.com/iam/docs/granting-roles-to-service-accounts) `Project -> Owner` +(**Note:** This step is unnecessary on Google platforms (Google App Engine / Google Cloud Shell / Google Compute Engine) as it will +automatically use the in-built Google credentials). Assign the [role](https://cloud.google.com/iam/docs/granting-roles-to-service-accounts) `Project -> Owner` and for Key type select JSON. Once you click `Create`, a JSON file containing your key is downloaded to your computer. Note down the path of this file or copy this file to the computer and file system where you will be running the example application as described later. Assume this JSON file is available at -`/path/to/JSON/file`. You can also use the `gcloud` shell commands to -[create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#iam-service-accounts-create-gcloud) -and [the JSON file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-gcloud). +`/path/to/JSON/file` Set the value of the environment variable GOOGLE_APPLICATION_CREDENTIALS to this file path. You can also use the `gcloud` shell commands to +[create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#iam-service-accounts-create-gcloud). #### To build the examples @@ -62,19 +62,18 @@ $ ../gradlew installDist #### How to run the example: -`google-auth-client` requires two command line arguments for the location of the JSON file and the project ID: +`google-auth-client` requires one command line argument for the project ID: ```text -USAGE: GoogleAuthClient +USAGE: GoogleAuthClient ``` -The first argument is the location of the JSON file you created in step 5 above. -The second argument is the project ID in the form "projects/xyz123" where "xyz123" is +The first argument is the project ID in the form "projects/xyz123" where "xyz123" is the project ID of the project you created (or used) in step 2 above. ```bash # Run the client -./build/install/example-gauth/bin/google-auth-client /path/to/JSON/file projects/xyz123 +./build/install/example-gauth/bin/google-auth-client projects/xyz123 ``` That's it! The client will show the list of Pub/Sub topics for the project as follows: @@ -93,7 +92,7 @@ the project ID of the project you created (or used) in step 2 above. ``` $ mvn verify $ # Run the client - $ mvn exec:java -Dexec.mainClass=io.grpc.examples.googleAuth.GoogleAuthClient -Dexec.args="/path/to/JSON/file projects/xyz123" + $ mvn exec:java -Dexec.mainClass=io.grpc.examples.googleAuth.GoogleAuthClient -Dexec.args="projects/xyz123" ``` ## Bazel @@ -101,5 +100,5 @@ the project ID of the project you created (or used) in step 2 above. ``` $ bazel build :google-auth-client $ # Run the client - $ ../bazel-bin/google-auth-client /path/to/JSON/file projects/xyz123 + $ ../bazel-bin/google-auth-client projects/xyz123 ``` \ No newline at end of file diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 47e812fde15..86edd557206 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index d2cba1a7959..89478a2dfa6 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java b/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java index 4d3dd044376..eb0d9feedfc 100644 --- a/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java +++ b/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java @@ -33,7 +33,7 @@ /** * Example to illustrate use of Google credentials as described in - * @see Google Auth Example README + * @see Google Auth Example README * * Also @see Google Cloud Pubsub via gRPC */ @@ -52,7 +52,7 @@ public class GoogleAuthClient { * * @param host host to connect to - typically "pubsub.googleapis.com" * @param port port to connect to - typically 443 - the TLS port - * @param callCredentials the Google call credentials created from a JSON file + * @param callCredentials the Google call credentials */ public GoogleAuthClient(String host, int port, CallCredentials callCredentials) { // Google API invocation requires a secure channel. Channels are secure by default (SSL/TLS) @@ -63,7 +63,7 @@ public GoogleAuthClient(String host, int port, CallCredentials callCredentials) * Construct our gRPC client that connects to the pubsub server using an existing channel. * * @param channel channel that has been built already - * @param callCredentials the Google call credentials created from a JSON file + * @param callCredentials the Google call credentials */ GoogleAuthClient(ManagedChannel channel, CallCredentials callCredentials) { this.channel = channel; @@ -101,32 +101,30 @@ public void getTopics(String projectID) { /** * The app requires 2 arguments as described in - * @see Google Auth Example README + * @see Google Auth Example README * - * arg0 = location of the JSON file for the service account you created in the GCP console - * arg1 = project name in the form "projects/balmy-cirrus-225307" where "balmy-cirrus-225307" is + * arg0 = project name in the form "projects/balmy-cirrus-225307" where "balmy-cirrus-225307" is * the project ID for the project you created. * + * On non-Google platforms, the GOOGLE_APPLICATION_CREDENTIALS env variable should be set to the + * location of the JSON file for the service account you created in the GCP console. */ public static void main(String[] args) throws Exception { - if (args.length < 2) { - logger.severe("Usage: please pass 2 arguments:\n" + - "arg0 = location of the JSON file for the service account you created in the GCP console\n" + - "arg1 = project name in the form \"projects/xyz\" where \"xyz\" is the project ID of the project you created.\n"); + if (args.length < 1) { + logger.severe("Usage: please pass 1 argument:\n" + + "arg0 = project name in the form \"projects/xyz\" where \"xyz\" is the project ID of the project you created.\n"); System.exit(1); } - GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(args[0])); + GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); // We need to create appropriate scope as per https://cloud.google.com/storage/docs/authentication#oauth-scopes credentials = credentials.createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform")); - // credentials must be refreshed before the access token is available - credentials.refreshAccessToken(); GoogleAuthClient client = new GoogleAuthClient("pubsub.googleapis.com", 443, MoreCallCredentials.from(credentials)); try { - client.getTopics(args[1]); + client.getTopics(args[0]); } finally { client.shutdown(); } diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index a392018ba25..179ab3a74d9 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index dcb8d420020..1a6f4719460 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index df8b0fde121..17817e2a374 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index c6d39887bac..991174af382 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index f996282bbb0..c31486095f3 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index c84f9893980..10330d955ed 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -14,8 +14,8 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 - 3.25.3 + 3.25.5 + 3.25.5 1.8 1.8 diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 7f600c2bc53..fe21efcf0db 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index fa2eaa41e36..072bd957dcf 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -14,8 +14,8 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 - 3.25.3 + 3.25.5 + 3.25.5 1.8 1.8 diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 21264ffcc17..f08f9d492a5 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index d087a532aff..4ca9343f887 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -19,7 +19,7 @@ java { } def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index d7d5c50b7e6..e1efc8ee050 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -19,7 +19,7 @@ java { } def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index 995e2d0979b..ebd14674578 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -17,7 +17,7 @@ java { } def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}", diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 8aad6b62bcb..8a16a902b72 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index e1d569a628c..972976ecdf1 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 8339db77e0c..a3e23a19601 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/pom.xml b/examples/pom.xml index 247df4a73ce..554c70a35ce 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -13,8 +13,8 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 - 3.25.3 + 3.25.5 + 3.25.5 1.8 1.8 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 488ead9ad86..8d7fb3766e0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ nettytcnative = '2.0.65.Final' opencensus = "0.31.1" # Not upgrading to 4.x as it is not yet ABI compatible. # https://github.com/protocolbuffers/protobuf/issues/17247 -protobuf = "3.25.3" +protobuf = "3.25.5" [libraries] android-annotations = "com.google.android:annotations:4.1.1.4" diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java index c195a78e6f4..1aa11ecf9af 100644 --- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java +++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java @@ -96,7 +96,6 @@ public void close(Executor instance) {} } @Captor private ArgumentCaptor resultCaptor; - @Captor private ArgumentCaptor errorCaptor; @Mock private ServiceConfigParser serviceConfigParser; @Mock private NameResolver.Listener2 mockListener; @@ -154,7 +153,7 @@ public List resolveSrv(String host) throws Exception { verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); assertThat(result.getAttributes()).isEqualTo(Attributes.EMPTY); assertThat(result.getServiceConfig()).isNull(); } @@ -196,7 +195,7 @@ public ConfigOrError answer(InvocationOnMock invocation) { ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( - Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + Iterables.getOnlyElement(result.getAddressesOrError().getValue()).getAddresses()); assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); EquivalentAddressGroup resolvedBalancerAddr = Iterables.getOnlyElement(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)); @@ -227,7 +226,7 @@ public void resolve_nullResourceResolver() throws Exception { assertThat(fakeClock.runDueTasks()).isEqualTo(1); verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); - assertThat(result.getAddresses()) + assertThat(result.getAddressesOrError().getValue()) .containsExactly( new EquivalentAddressGroup(new InetSocketAddress(backendAddr, DEFAULT_PORT))); assertThat(result.getAttributes()).isEqualTo(Attributes.EMPTY); @@ -245,8 +244,8 @@ public void resolve_nullResourceResolver_addressFailure() throws Exception { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onError(errorCaptor.capture()); - Status errorStatus = errorCaptor.getValue(); + verify(mockListener).onResult2(resultCaptor.capture()); + Status errorStatus = resultCaptor.getValue().getAddressesOrError().getStatus(); assertThat(errorStatus.getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(errorStatus.getCause()).hasMessageThat().contains("no addr"); } @@ -274,7 +273,7 @@ public void resolve_addressFailure_stillLookUpBalancersAndServiceConfig() throws assertThat(fakeClock.runDueTasks()).isEqualTo(1); verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); EquivalentAddressGroup resolvedBalancerAddr = Iterables.getOnlyElement(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)); assertThat(resolvedBalancerAddr.getAttributes().get(GrpclbConstants.ATTR_LB_ADDR_AUTHORITY)) @@ -311,7 +310,7 @@ public void resolveAll_balancerLookupFails_stillLookUpServiceConfig() throws Exc InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( - Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + Iterables.getOnlyElement(result.getAddressesOrError().getValue()).getAddresses()); assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); assertThat(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)).isNull(); verify(mockAddressResolver).resolveAddress(hostName); @@ -335,8 +334,8 @@ public void resolve_addressAndBalancersLookupFail_neverLookupServiceConfig() thr resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onError(errorCaptor.capture()); - Status errorStatus = errorCaptor.getValue(); + verify(mockListener).onResult2(resultCaptor.capture()); + Status errorStatus = resultCaptor.getValue().getAddressesOrError().getStatus(); assertThat(errorStatus.getCode()).isEqualTo(Code.UNAVAILABLE); verify(mockAddressResolver).resolveAddress(hostName); verify(mockResourceResolver, never()).resolveTxt("_grpc_config." + hostName); diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index a19efb00155..a85aec97adf 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation project(path: ':grpc-alts', configuration: 'shadow'), project(':grpc-auth'), project(':grpc-census'), + project(':grpc-opentelemetry'), project(':grpc-gcp-csm-observability'), project(':grpc-netty'), project(':grpc-okhttp'), diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java new file mode 100644 index 00000000000..3884d977a6e --- /dev/null +++ b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java @@ -0,0 +1,191 @@ +/* + * Copyright 2024 The gRPC Authors + * + * 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 io.grpc.testing.integration; + +import static org.junit.Assert.assertEquals; + +import io.grpc.ForwardingServerCallListener; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.ServerBuilder; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.netty.InternalNettyChannelBuilder; +import io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.opentelemetry.GrpcOpenTelemetry; +import io.grpc.opentelemetry.GrpcTraceBinContextPropagator; +import io.grpc.opentelemetry.InternalGrpcOpenTelemetry; +import io.grpc.testing.integration.Messages.SimpleRequest; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class OpenTelemetryContextPropagationTest extends AbstractInteropTest { + private final OpenTelemetrySdk openTelemetrySdk; + private final Tracer tracer; + private final GrpcOpenTelemetry grpcOpenTelemetry; + private final AtomicReference applicationSpan = new AtomicReference<>(); + private final boolean censusClient; + + @Parameterized.Parameters(name = "ContextPropagator={0}, CensusClient={1}") + public static Iterable data() { + return Arrays.asList(new Object[][] { + {W3CTraceContextPropagator.getInstance(), false}, + {GrpcTraceBinContextPropagator.defaultInstance(), false}, + {GrpcTraceBinContextPropagator.defaultInstance(), true} + }); + } + + public OpenTelemetryContextPropagationTest(TextMapPropagator textMapPropagator, + boolean isCensusClient) { + this.openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .setPropagators(ContextPropagators.create(TextMapPropagator.composite( + textMapPropagator + ))) + .build(); + this.tracer = openTelemetrySdk + .getTracer("grpc-java-interop-test"); + GrpcOpenTelemetry.Builder grpcOpentelemetryBuilder = GrpcOpenTelemetry.newBuilder() + .sdk(openTelemetrySdk); + InternalGrpcOpenTelemetry.enableTracing(grpcOpentelemetryBuilder, true); + grpcOpenTelemetry = grpcOpentelemetryBuilder.build(); + this.censusClient = isCensusClient; + } + + @Override + protected ServerBuilder getServerBuilder() { + NettyServerBuilder builder = NettyServerBuilder.forPort(0, InsecureServerCredentials.create()) + .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE); + builder.intercept(new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + ServerCall.Listener listener = next.startCall(call, headers); + return new ForwardingServerCallListener() { + @Override + protected ServerCall.Listener delegate() { + return listener; + } + + @Override + public void onMessage(ReqT request) { + applicationSpan.set(tracer.spanBuilder("InteropTest.Application.Span").startSpan()); + delegate().onMessage(request); + } + + @Override + public void onHalfClose() { + maybeCloseSpan(applicationSpan); + delegate().onHalfClose(); + } + + @Override + public void onCancel() { + maybeCloseSpan(applicationSpan); + delegate().onCancel(); + } + + @Override + public void onComplete() { + maybeCloseSpan(applicationSpan); + delegate().onComplete(); + } + }; + } + }); + // To ensure proper propagation of remote spans from gRPC to your application, this interceptor + // must be after any application interceptors that interact with spans. This allows the tracing + // information to be correctly passed along. However, it's fine for application-level onMessage + // handlers to access the span. + grpcOpenTelemetry.configureServerBuilder(builder); + return builder; + } + + private void maybeCloseSpan(AtomicReference applicationSpan) { + Span tmp = applicationSpan.get(); + if (tmp != null) { + tmp.end(); + } + } + + @Override + protected boolean metricsExpected() { + return false; + } + + @Override + protected ManagedChannelBuilder createChannelBuilder() { + NettyChannelBuilder builder = NettyChannelBuilder.forAddress(getListenAddress()) + .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE) + .usePlaintext(); + if (!censusClient) { + // Disabling census-tracing is necessary to avoid trace ID mismatches. + // This is because census-tracing overrides the grpc-trace-bin header with + // OpenTelemetry's GrpcTraceBinPropagator. + InternalNettyChannelBuilder.setTracingEnabled(builder, false); + grpcOpenTelemetry.configureChannelBuilder(builder); + } + return builder; + } + + @Test + public void otelSpanContextPropagation() { + Assume.assumeFalse(censusClient); + Span parentSpan = tracer.spanBuilder("Test.interopTest").startSpan(); + try (Scope scope = Context.current().with(parentSpan).makeCurrent()) { + blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); + } + assertEquals(parentSpan.getSpanContext().getTraceId(), + applicationSpan.get().getSpanContext().getTraceId()); + } + + @Test + @SuppressWarnings("deprecation") + public void censusToOtelGrpcTraceBinPropagator() { + Assume.assumeTrue(censusClient); + io.opencensus.trace.Tracer censusTracer = io.opencensus.trace.Tracing.getTracer(); + io.opencensus.trace.Span parentSpan = censusTracer.spanBuilder("Test.interopTest") + .startSpan(); + io.grpc.Context context = io.opencensus.trace.unsafe.ContextUtils.withValue( + io.grpc.Context.current(), parentSpan); + io.grpc.Context previous = context.attach(); + try { + blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); + assertEquals(parentSpan.getContext().getTraceId().toLowerBase16(), + applicationSpan.get().getSpanContext().getTraceId()); + } finally { + context.detach(previous); + } + } +} diff --git a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java index 0d309828c6d..b9c6a77982a 100644 --- a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java @@ -17,12 +17,14 @@ package io.grpc.netty; import io.grpc.ChannelLogger; +import io.grpc.internal.ObjectPool; import io.grpc.netty.ProtocolNegotiators.ClientTlsHandler; import io.grpc.netty.ProtocolNegotiators.GrpcNegotiationHandler; import io.grpc.netty.ProtocolNegotiators.WaitUntilActiveHandler; import io.netty.channel.ChannelHandler; import io.netty.handler.ssl.SslContext; import io.netty.util.AsciiString; +import java.util.concurrent.Executor; /** * Internal accessor for {@link ProtocolNegotiators}. @@ -35,9 +37,12 @@ private InternalProtocolNegotiators() {} * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} * may happen immediately, even before the TLS Handshake is complete. + * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks */ - public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslContext) { - final io.grpc.netty.ProtocolNegotiator negotiator = ProtocolNegotiators.tls(sslContext); + public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslContext, + ObjectPool executorPool) { + final io.grpc.netty.ProtocolNegotiator negotiator = ProtocolNegotiators.tls(sslContext, + executorPool); final class TlsNegotiator implements InternalProtocolNegotiator.ProtocolNegotiator { @Override @@ -58,6 +63,15 @@ public void close() { return new TlsNegotiator(); } + + /** + * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will + * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} + * may happen immediately, even before the TLS Handshake is complete. + */ + public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslContext) { + return tls(sslContext, null); + } /** * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will be diff --git a/netty/src/main/java/io/grpc/netty/UdsNameResolver.java b/netty/src/main/java/io/grpc/netty/UdsNameResolver.java index 8fa8ea06250..de14dc8b460 100644 --- a/netty/src/main/java/io/grpc/netty/UdsNameResolver.java +++ b/netty/src/main/java/io/grpc/netty/UdsNameResolver.java @@ -22,16 +22,16 @@ import com.google.common.base.Preconditions; import io.grpc.EquivalentAddressGroup; import io.grpc.NameResolver; +import io.grpc.StatusOr; import io.netty.channel.unix.DomainSocketAddress; import java.util.ArrayList; -import java.util.Collections; import java.util.List; final class UdsNameResolver extends NameResolver { private NameResolver.Listener2 listener; private final String authority; - UdsNameResolver(String authority, String targetPath) { + UdsNameResolver(String authority, String targetPath, Args args) { checkArgument(authority == null, "non-null authority not supported"); this.authority = targetPath; } @@ -57,8 +57,8 @@ private void resolve() { ResolutionResult.Builder resolutionResultBuilder = ResolutionResult.newBuilder(); List servers = new ArrayList<>(1); servers.add(new EquivalentAddressGroup(new DomainSocketAddress(authority))); - resolutionResultBuilder.setAddresses(Collections.unmodifiableList(servers)); - listener.onResult(resolutionResultBuilder.build()); + resolutionResultBuilder.setAddressesOrError(StatusOr.fromValue(servers)); + listener.onResult2(resolutionResultBuilder.build()); } @Override diff --git a/netty/src/main/java/io/grpc/netty/UdsNameResolverProvider.java b/netty/src/main/java/io/grpc/netty/UdsNameResolverProvider.java index 9f594193b4c..fe6300057fd 100644 --- a/netty/src/main/java/io/grpc/netty/UdsNameResolverProvider.java +++ b/netty/src/main/java/io/grpc/netty/UdsNameResolverProvider.java @@ -34,7 +34,7 @@ public final class UdsNameResolverProvider extends NameResolverProvider { @Override public UdsNameResolver newNameResolver(URI targetUri, NameResolver.Args args) { if (SCHEME.equals(targetUri.getScheme())) { - return new UdsNameResolver(targetUri.getAuthority(), getTargetPathFromUri(targetUri)); + return new UdsNameResolver(targetUri.getAuthority(), getTargetPathFromUri(targetUri), args); } else { return null; } diff --git a/netty/src/test/java/io/grpc/netty/UdsNameResolverProviderTest.java b/netty/src/test/java/io/grpc/netty/UdsNameResolverProviderTest.java index 6a329c8fc68..9dacf00cfad 100644 --- a/netty/src/test/java/io/grpc/netty/UdsNameResolverProviderTest.java +++ b/netty/src/test/java/io/grpc/netty/UdsNameResolverProviderTest.java @@ -18,10 +18,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import io.grpc.ChannelLogger; import io.grpc.EquivalentAddressGroup; import io.grpc.NameResolver; +import io.grpc.NameResolver.ServiceConfigParser; +import io.grpc.SynchronizationContext; +import io.grpc.internal.FakeClock; +import io.grpc.internal.GrpcUtil; import io.netty.channel.unix.DomainSocketAddress; import java.net.SocketAddress; import java.net.URI; @@ -39,7 +45,7 @@ /** Unit tests for {@link UdsNameResolverProvider}. */ @RunWith(JUnit4.class) public class UdsNameResolverProviderTest { - + private static final int DEFAULT_PORT = 887; @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @@ -51,16 +57,29 @@ public class UdsNameResolverProviderTest { UdsNameResolverProvider udsNameResolverProvider = new UdsNameResolverProvider(); + private final SynchronizationContext syncContext = new SynchronizationContext( + (t, e) -> { + throw new AssertionError(e); + }); + private final FakeClock fakeExecutor = new FakeClock(); + private final NameResolver.Args args = NameResolver.Args.newBuilder() + .setDefaultPort(DEFAULT_PORT) + .setProxyDetector(GrpcUtil.DEFAULT_PROXY_DETECTOR) + .setSynchronizationContext(syncContext) + .setServiceConfigParser(mock(ServiceConfigParser.class)) + .setChannelLogger(mock(ChannelLogger.class)) + .setScheduledExecutorService(fakeExecutor.getScheduledExecutorService()) + .build(); @Test public void testUnixRelativePath() { UdsNameResolver udsNameResolver = - udsNameResolverProvider.newNameResolver(URI.create("unix:sock.sock"), null); + udsNameResolverProvider.newNameResolver(URI.create("unix:sock.sock"), args); assertThat(udsNameResolver).isNotNull(); udsNameResolver.start(mockListener); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); NameResolver.ResolutionResult result = resultCaptor.getValue(); - List list = result.getAddresses(); + List list = result.getAddressesOrError().getValue(); assertThat(list).isNotNull(); assertThat(list).hasSize(1); EquivalentAddressGroup eag = list.get(0); @@ -75,12 +94,12 @@ public void testUnixRelativePath() { @Test public void testUnixAbsolutePath() { UdsNameResolver udsNameResolver = - udsNameResolverProvider.newNameResolver(URI.create("unix:/sock.sock"), null); + udsNameResolverProvider.newNameResolver(URI.create("unix:/sock.sock"), args); assertThat(udsNameResolver).isNotNull(); udsNameResolver.start(mockListener); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); NameResolver.ResolutionResult result = resultCaptor.getValue(); - List list = result.getAddresses(); + List list = result.getAddressesOrError().getValue(); assertThat(list).isNotNull(); assertThat(list).hasSize(1); EquivalentAddressGroup eag = list.get(0); @@ -95,12 +114,12 @@ public void testUnixAbsolutePath() { @Test public void testUnixAbsoluteAlternatePath() { UdsNameResolver udsNameResolver = - udsNameResolverProvider.newNameResolver(URI.create("unix:///sock.sock"), null); + udsNameResolverProvider.newNameResolver(URI.create("unix:///sock.sock"), args); assertThat(udsNameResolver).isNotNull(); udsNameResolver.start(mockListener); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); NameResolver.ResolutionResult result = resultCaptor.getValue(); - List list = result.getAddresses(); + List list = result.getAddressesOrError().getValue(); assertThat(list).isNotNull(); assertThat(list).hasSize(1); EquivalentAddressGroup eag = list.get(0); @@ -115,7 +134,7 @@ public void testUnixAbsoluteAlternatePath() { @Test public void testUnixPathWithAuthority() { try { - udsNameResolverProvider.newNameResolver(URI.create("unix://localhost/sock.sock"), null); + udsNameResolverProvider.newNameResolver(URI.create("unix://localhost/sock.sock"), args); fail("exception expected"); } catch (IllegalArgumentException e) { assertThat(e).hasMessageThat().isEqualTo("non-null authority not supported"); diff --git a/netty/src/test/java/io/grpc/netty/UdsNameResolverTest.java b/netty/src/test/java/io/grpc/netty/UdsNameResolverTest.java index 8eb010e23e5..22790a41c77 100644 --- a/netty/src/test/java/io/grpc/netty/UdsNameResolverTest.java +++ b/netty/src/test/java/io/grpc/netty/UdsNameResolverTest.java @@ -18,10 +18,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import io.grpc.ChannelLogger; import io.grpc.EquivalentAddressGroup; import io.grpc.NameResolver; +import io.grpc.NameResolver.ServiceConfigParser; +import io.grpc.SynchronizationContext; +import io.grpc.internal.FakeClock; +import io.grpc.internal.GrpcUtil; import io.netty.channel.unix.DomainSocketAddress; import java.net.SocketAddress; import java.util.List; @@ -41,7 +47,20 @@ public class UdsNameResolverTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); - + private static final int DEFAULT_PORT = 887; + private final FakeClock fakeExecutor = new FakeClock(); + private final SynchronizationContext syncContext = new SynchronizationContext( + (t, e) -> { + throw new AssertionError(e); + }); + private final NameResolver.Args args = NameResolver.Args.newBuilder() + .setDefaultPort(DEFAULT_PORT) + .setProxyDetector(GrpcUtil.DEFAULT_PROXY_DETECTOR) + .setSynchronizationContext(syncContext) + .setServiceConfigParser(mock(ServiceConfigParser.class)) + .setChannelLogger(mock(ChannelLogger.class)) + .setScheduledExecutorService(fakeExecutor.getScheduledExecutorService()) + .build(); @Mock private NameResolver.Listener2 mockListener; @@ -52,11 +71,11 @@ public class UdsNameResolverTest { @Test public void testValidTargetPath() { - udsNameResolver = new UdsNameResolver(null, "sock.sock"); + udsNameResolver = new UdsNameResolver(null, "sock.sock", args); udsNameResolver.start(mockListener); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); NameResolver.ResolutionResult result = resultCaptor.getValue(); - List list = result.getAddresses(); + List list = result.getAddressesOrError().getValue(); assertThat(list).isNotNull(); assertThat(list).hasSize(1); EquivalentAddressGroup eag = list.get(0); @@ -72,7 +91,7 @@ public void testValidTargetPath() { @Test public void testNonNullAuthority() { try { - udsNameResolver = new UdsNameResolver("authority", "sock.sock"); + udsNameResolver = new UdsNameResolver("authority", "sock.sock", args); fail("exception expected"); } catch (IllegalArgumentException e) { assertThat(e).hasMessageThat().isEqualTo("non-null authority not supported"); diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java index 6ed3bc50b81..29ea8055b26 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java @@ -283,7 +283,7 @@ private static boolean isAtLeastAndroid41() { /** * Select the first recognized security provider according to the preference order returned by - * {@link Security#getProviders}. If a recognized provider is not found then warn but continue. + * {@link Security#getProviders}. */ private static Provider getAndroidSecurityProvider() { Provider[] providers = Security.getProviders(); @@ -295,7 +295,6 @@ private static Provider getAndroidSecurityProvider() { } } } - logger.log(Level.WARNING, "Unable to find Conscrypt"); return null; } diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java index 6f2d3268ae0..838ee0797a7 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java @@ -217,7 +217,6 @@ public void outboundMessageSent( @Override public void inboundMessageRead( int seqNo, long optionalWireSize, long optionalUncompressedSize) { - //TODO(yifeizhuang): needs support from message deframer. if (optionalWireSize != optionalUncompressedSize) { recordInboundCompressedMessage(span, seqNo, optionalWireSize); } diff --git a/s2a/BUILD.bazel b/s2a/BUILD.bazel index 5aeaedbe358..283828a9f7e 100644 --- a/s2a/BUILD.bazel +++ b/s2a/BUILD.bazel @@ -5,7 +5,7 @@ load("@rules_jvm_external//:defs.bzl", "artifact") java_library( name = "s2a_channel_pool", srcs = glob([ - "src/main/java/io/grpc/s2a/channel/*.java", + "src/main/java/io/grpc/s2a/internal/channel/*.java", ]), deps = [ "//api", @@ -23,7 +23,7 @@ java_library( java_library( name = "s2a_identity", - srcs = ["src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java"], + srcs = ["src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java"], deps = [ ":common_java_proto", artifact("com.google.errorprone:error_prone_annotations"), @@ -32,33 +32,13 @@ java_library( ) java_library( - name = "token_fetcher", - srcs = ["src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java"], - deps = [ - ":s2a_identity", - ], -) - -java_library( - name = "access_token_manager", - srcs = [ - "src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java", - ], + name = "token_manager", + srcs = glob([ + "src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/*.java", + ]), deps = [ ":s2a_identity", - ":token_fetcher", artifact("com.google.code.findbugs:jsr305"), - ], -) - -java_library( - name = "single_token_fetcher", - srcs = [ - "src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java", - ], - deps = [ - ":s2a_identity", - ":token_fetcher", artifact("com.google.guava:guava"), ], ) @@ -66,24 +46,23 @@ java_library( java_library( name = "s2a_handshaker", srcs = [ - "src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java", - "src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java", - "src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java", - "src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java", - "src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java", - "src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java", - "src/main/java/io/grpc/s2a/handshaker/S2AStub.java", - "src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java", - "src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java", + "src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java", + "src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java", + "src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java", + "src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java", ], deps = [ - ":access_token_manager", + ":token_manager", ":common_java_proto", ":s2a_channel_pool", ":s2a_identity", ":s2a_java_proto", ":s2a_java_grpc_proto", - ":single_token_fetcher", "//api", "//core:internal", "//netty", @@ -117,19 +96,6 @@ java_library( ], ) -java_library( - name = "mtls_to_s2av2_credentials", - srcs = ["src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java"], - visibility = ["//visibility:public"], - deps = [ - ":s2a_channel_pool", - ":s2av2_credentials", - "//api", - "//util", - artifact("com.google.guava:guava"), - ], -) - # bazel only accepts proto import with absolute path. genrule( name = "protobuf_imports", diff --git a/s2a/build.gradle b/s2a/build.gradle index 234f983fd5c..af2c879a753 100644 --- a/s2a/build.gradle +++ b/s2a/build.gradle @@ -1,6 +1,5 @@ plugins { id "java-library" - id "maven-publish" id "com.github.johnrengelman.shadow" id "com.google.osdetector" @@ -90,6 +89,7 @@ tasks.named("shadowJar").configure { relocate 'io.netty', 'io.grpc.netty.shaded.io.netty' } +plugins.withId('maven-publish') { publishing { publications { maven(MavenPublication) { @@ -111,3 +111,4 @@ publishing { } } } +} diff --git a/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java b/s2a/src/generated/main/grpc/io/grpc/s2a/internal/handshaker/S2AServiceGrpc.java similarity index 84% rename from s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java rename to s2a/src/generated/main/grpc/io/grpc/s2a/internal/handshaker/S2AServiceGrpc.java index b365954b189..d759128a4c5 100644 --- a/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java +++ b/s2a/src/generated/main/grpc/io/grpc/s2a/internal/handshaker/S2AServiceGrpc.java @@ -1,4 +1,4 @@ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static io.grpc.MethodDescriptor.generateFullMethodName; @@ -15,29 +15,29 @@ private S2AServiceGrpc() {} public static final java.lang.String SERVICE_NAME = "grpc.gcp.s2a.S2AService"; // Static method descriptors that strictly reflect the proto. - private static volatile io.grpc.MethodDescriptor getSetUpSessionMethod; + private static volatile io.grpc.MethodDescriptor getSetUpSessionMethod; @io.grpc.stub.annotations.RpcMethod( fullMethodName = SERVICE_NAME + '/' + "SetUpSession", - requestType = io.grpc.s2a.handshaker.SessionReq.class, - responseType = io.grpc.s2a.handshaker.SessionResp.class, + requestType = io.grpc.s2a.internal.handshaker.SessionReq.class, + responseType = io.grpc.s2a.internal.handshaker.SessionResp.class, methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) - public static io.grpc.MethodDescriptor getSetUpSessionMethod() { - io.grpc.MethodDescriptor getSetUpSessionMethod; + public static io.grpc.MethodDescriptor getSetUpSessionMethod() { + io.grpc.MethodDescriptor getSetUpSessionMethod; if ((getSetUpSessionMethod = S2AServiceGrpc.getSetUpSessionMethod) == null) { synchronized (S2AServiceGrpc.class) { if ((getSetUpSessionMethod = S2AServiceGrpc.getSetUpSessionMethod) == null) { S2AServiceGrpc.getSetUpSessionMethod = getSetUpSessionMethod = - io.grpc.MethodDescriptor.newBuilder() + io.grpc.MethodDescriptor.newBuilder() .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SetUpSession")) .setSampledToLocalTracing(true) .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - io.grpc.s2a.handshaker.SessionReq.getDefaultInstance())) + io.grpc.s2a.internal.handshaker.SessionReq.getDefaultInstance())) .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - io.grpc.s2a.handshaker.SessionResp.getDefaultInstance())) + io.grpc.s2a.internal.handshaker.SessionResp.getDefaultInstance())) .setSchemaDescriptor(new S2AServiceMethodDescriptorSupplier("SetUpSession")) .build(); } @@ -100,8 +100,8 @@ public interface AsyncService { * operations from the TLS handshake. * */ - default io.grpc.stub.StreamObserver setUpSession( - io.grpc.stub.StreamObserver responseObserver) { + default io.grpc.stub.StreamObserver setUpSession( + io.grpc.stub.StreamObserver responseObserver) { return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSetUpSessionMethod(), responseObserver); } } @@ -139,8 +139,8 @@ protected S2AServiceStub build( * operations from the TLS handshake. * */ - public io.grpc.stub.StreamObserver setUpSession( - io.grpc.stub.StreamObserver responseObserver) { + public io.grpc.stub.StreamObserver setUpSession( + io.grpc.stub.StreamObserver responseObserver) { return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( getChannel().newCall(getSetUpSessionMethod(), getCallOptions()), responseObserver); } @@ -211,7 +211,7 @@ public io.grpc.stub.StreamObserver invoke( switch (methodId) { case METHODID_SET_UP_SESSION: return (io.grpc.stub.StreamObserver) serviceImpl.setUpSession( - (io.grpc.stub.StreamObserver) responseObserver); + (io.grpc.stub.StreamObserver) responseObserver); default: throw new AssertionError(); } @@ -224,8 +224,8 @@ public static final io.grpc.ServerServiceDefinition bindService(AsyncService ser getSetUpSessionMethod(), io.grpc.stub.ServerCalls.asyncBidiStreamingCall( new MethodHandlers< - io.grpc.s2a.handshaker.SessionReq, - io.grpc.s2a.handshaker.SessionResp>( + io.grpc.s2a.internal.handshaker.SessionReq, + io.grpc.s2a.internal.handshaker.SessionResp>( service, METHODID_SET_UP_SESSION))) .build(); } @@ -236,7 +236,7 @@ private static abstract class S2AServiceBaseDescriptorSupplier @java.lang.Override public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { - return io.grpc.s2a.handshaker.S2AProto.getDescriptor(); + return io.grpc.s2a.internal.handshaker.S2AProto.getDescriptor(); } @java.lang.Override diff --git a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java deleted file mode 100644 index 56f612502bf..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * 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 io.grpc.s2a; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Strings.isNullOrEmpty; - -import io.grpc.ChannelCredentials; -import io.grpc.TlsChannelCredentials; -import io.grpc.util.AdvancedTlsX509KeyManager; -import io.grpc.util.AdvancedTlsX509TrustManager; -import java.io.File; -import java.io.IOException; -import java.security.GeneralSecurityException; - -/** - * Configures an {@code S2AChannelCredentials.Builder} instance with credentials used to establish a - * connection with the S2A to support talking to the S2A over mTLS. - */ -public final class MtlsToS2AChannelCredentials { - /** - * Creates a {@code S2AChannelCredentials.Builder} builder, that talks to the S2A over mTLS. - * - * @param s2aAddress the address of the S2A server used to secure the connection. - * @param privateKeyPath the path to the private key PEM to use for authenticating to the S2A. - * @param certChainPath the path to the cert chain PEM to use for authenticating to the S2A. - * @param trustBundlePath the path to the trust bundle PEM. - * @return a {@code MtlsToS2AChannelCredentials.Builder} instance. - */ - public static Builder createBuilder( - String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { - checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); - checkArgument(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); - checkArgument(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); - checkArgument(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty."); - return new Builder(s2aAddress, privateKeyPath, certChainPath, trustBundlePath); - } - - /** Builds an {@code MtlsToS2AChannelCredentials} instance. */ - public static final class Builder { - private final String s2aAddress; - private final String privateKeyPath; - private final String certChainPath; - private final String trustBundlePath; - - Builder( - String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { - this.s2aAddress = s2aAddress; - this.privateKeyPath = privateKeyPath; - this.certChainPath = certChainPath; - this.trustBundlePath = trustBundlePath; - } - - public S2AChannelCredentials.Builder build() throws GeneralSecurityException, IOException { - checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); - checkState(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); - checkState(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); - checkState(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty."); - File privateKeyFile = new File(privateKeyPath); - File certChainFile = new File(certChainPath); - File trustBundleFile = new File(trustBundlePath); - - AdvancedTlsX509KeyManager keyManager = new AdvancedTlsX509KeyManager(); - keyManager.updateIdentityCredentials(certChainFile, privateKeyFile); - - AdvancedTlsX509TrustManager trustManager = AdvancedTlsX509TrustManager.newBuilder().build(); - trustManager.updateTrustCredentials(trustBundleFile); - - ChannelCredentials channelToS2ACredentials = - TlsChannelCredentials.newBuilder() - .keyManager(keyManager) - .trustManager(trustManager) - .build(); - - return S2AChannelCredentials.createBuilder(s2aAddress) - .setS2AChannelCredentials(channelToS2ACredentials); - } - } - - private MtlsToS2AChannelCredentials() {} -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java index 8a5f1f51350..2e040964dfa 100644 --- a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java +++ b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java @@ -18,20 +18,19 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.grpc.Channel; import io.grpc.ChannelCredentials; +import io.grpc.ExperimentalApi; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; import io.grpc.netty.InternalNettyChannelCredentials; import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory; -import java.util.Optional; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AProtocolNegotiatorFactory; import javax.annotation.concurrent.NotThreadSafe; import org.checkerframework.checker.nullness.qual.Nullable; @@ -39,30 +38,31 @@ * Configures gRPC to use S2A for transport security when establishing a secure channel. Only for * use on the client side of a gRPC connection. */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11533") public final class S2AChannelCredentials { /** * Creates a channel credentials builder for establishing an S2A-secured connection. * * @param s2aAddress the address of the S2A server used to secure the connection. + * @param s2aChannelCredentials the credentials to be used when connecting to the S2A. * @return a {@code S2AChannelCredentials.Builder} instance. */ - public static Builder createBuilder(String s2aAddress) { + public static Builder newBuilder(String s2aAddress, ChannelCredentials s2aChannelCredentials) { checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); - return new Builder(s2aAddress); + checkNotNull(s2aChannelCredentials, "S2A channel credentials must not be null"); + return new Builder(s2aAddress, s2aChannelCredentials); } /** Builds an {@code S2AChannelCredentials} instance. */ @NotThreadSafe public static final class Builder { private final String s2aAddress; - private ObjectPool s2aChannelPool; - private Optional s2aChannelCredentials; + private final ChannelCredentials s2aChannelCredentials; private @Nullable S2AIdentity localIdentity = null; - Builder(String s2aAddress) { + Builder(String s2aAddress, ChannelCredentials s2aChannelCredentials) { this.s2aAddress = s2aAddress; - this.s2aChannelPool = null; - this.s2aChannelCredentials = Optional.empty(); + this.s2aChannelCredentials = s2aChannelCredentials; } /** @@ -104,24 +104,15 @@ public Builder setLocalUid(String localUid) { return this; } - /** Sets the credentials to be used when connecting to the S2A. */ - @CanIgnoreReturnValue - public Builder setS2AChannelCredentials(ChannelCredentials s2aChannelCredentials) { - this.s2aChannelCredentials = Optional.of(s2aChannelCredentials); - return this; + public ChannelCredentials build() { + return InternalNettyChannelCredentials.create(buildProtocolNegotiatorFactory()); } - public ChannelCredentials build() { - checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); + InternalProtocolNegotiator.ClientFactory buildProtocolNegotiatorFactory() { ObjectPool s2aChannelPool = SharedResourcePool.forResource( S2AHandshakerServiceChannel.getChannelResource(s2aAddress, s2aChannelCredentials)); checkNotNull(s2aChannelPool, "s2aChannelPool"); - this.s2aChannelPool = s2aChannelPool; - return InternalNettyChannelCredentials.create(buildProtocolNegotiatorFactory()); - } - - InternalProtocolNegotiator.ClientFactory buildProtocolNegotiatorFactory() { return S2AProtocolNegotiatorFactory.createClientFactory(localIdentity, s2aChannelPool); } } diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java deleted file mode 100644 index e5caf5e69bd..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * 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 io.grpc.s2a.channel; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.grpc.Channel; -import javax.annotation.concurrent.ThreadSafe; - -/** Manages a channel pool to be used for communication with the S2A. */ -@ThreadSafe -public interface S2AChannelPool extends AutoCloseable { - /** - * Retrieves an open channel to the S2A from the channel pool. - * - * @throws IllegalStateException if no channel is available. - */ - @CanIgnoreReturnValue - Channel getChannel(); - - /** Returns a channel to the channel pool. */ - void returnToPool(Channel channel); - - /** - * Returns all channels to the channel pool and closes the pool so that no new channels can be - * retrieved from the pool. - */ - @Override - void close(); -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java deleted file mode 100644 index 4794cd9ee49..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * 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 io.grpc.s2a.channel; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import com.google.errorprone.annotations.concurrent.GuardedBy; -import io.grpc.Channel; -import io.grpc.internal.ObjectPool; -import javax.annotation.concurrent.ThreadSafe; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * Manages a gRPC channel pool and a cached gRPC channel to be used for communication with the S2A. - */ -@ThreadSafe -public final class S2AGrpcChannelPool implements S2AChannelPool { - private static final int MAX_NUMBER_USERS_OF_CACHED_CHANNEL = 100000; - private final ObjectPool channelPool; - - @GuardedBy("this") - private @Nullable Channel cachedChannel; - - @GuardedBy("this") - private int numberOfUsersOfCachedChannel = 0; - - private enum State { - OPEN, - CLOSED, - } - - ; - - @GuardedBy("this") - private State state = State.OPEN; - - public static S2AChannelPool create(ObjectPool channelPool) { - checkNotNull(channelPool, "Channel pool should not be null."); - return new S2AGrpcChannelPool(channelPool); - } - - private S2AGrpcChannelPool(ObjectPool channelPool) { - this.channelPool = channelPool; - } - - /** - * Retrieves a channel from {@code channelPool} if {@code channel} is null, and returns {@code - * channel} otherwise. - * - * @return a {@link Channel} obtained from the channel pool. - */ - @Override - public synchronized Channel getChannel() { - checkState(state.equals(State.OPEN), "Channel pool is not open."); - checkState( - numberOfUsersOfCachedChannel < MAX_NUMBER_USERS_OF_CACHED_CHANNEL, - "Max number of channels have been retrieved from the channel pool."); - if (cachedChannel == null) { - cachedChannel = channelPool.getObject(); - } - numberOfUsersOfCachedChannel += 1; - return cachedChannel; - } - - /** - * Returns {@code channel} to {@code channelPool}. - * - *

The caller must ensure that {@code channel} was retrieved from this channel pool. - */ - @Override - public synchronized void returnToPool(Channel channel) { - checkState(state.equals(State.OPEN), "Channel pool is not open."); - checkArgument( - cachedChannel != null && numberOfUsersOfCachedChannel > 0 && cachedChannel.equals(channel), - "Cannot return the channel to channel pool because the channel was not obtained from" - + " channel pool."); - numberOfUsersOfCachedChannel -= 1; - if (numberOfUsersOfCachedChannel == 0) { - channelPool.returnObject(channel); - cachedChannel = null; - } - } - - @Override - public synchronized void close() { - state = State.CLOSED; - numberOfUsersOfCachedChannel = 0; - if (cachedChannel != null) { - channelPool.returnObject(cachedChannel); - cachedChannel = null; - } - } -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java deleted file mode 100644 index 75ec7347bb5..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * 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 io.grpc.s2a.channel; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ChannelCredentials; -import io.grpc.ClientCall; -import io.grpc.ManagedChannel; -import io.grpc.MethodDescriptor; -import io.grpc.internal.SharedResourceHolder.Resource; -import io.grpc.netty.NettyChannelBuilder; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.util.concurrent.DefaultThreadFactory; -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.ConcurrentMap; -import javax.annotation.concurrent.ThreadSafe; - -/** - * Provides APIs for managing gRPC channels to S2A servers. Each channel is local and plaintext. If - * credentials are provided, they are used to secure the channel. - * - *

This is done as follows: for each S2A server, provides an implementation of gRPC's {@link - * SharedResourceHolder.Resource} interface called a {@code Resource}. A {@code - * Resource} is a factory for creating gRPC channels to the S2A server at a given address, - * and a channel must be returned to the {@code Resource} when it is no longer needed. - * - *

Typical usage pattern is below: - * - *

{@code
- * Resource resource = S2AHandshakerServiceChannel.getChannelResource("localhost:1234",
- * creds);
- * Channel channel = resource.create();
- * // Send an RPC over the channel to the S2A server running at localhost:1234.
- * resource.close(channel);
- * }
- */ -@ThreadSafe -public final class S2AHandshakerServiceChannel { - private static final ConcurrentMap> SHARED_RESOURCE_CHANNELS = - Maps.newConcurrentMap(); - private static final Duration DELEGATE_TERMINATION_TIMEOUT = Duration.ofSeconds(2); - private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); - - /** - * Returns a {@link SharedResourceHolder.Resource} instance for managing channels to an S2A server - * running at {@code s2aAddress}. - * - * @param s2aAddress the address of the S2A, typically in the format {@code host:port}. - * @param s2aChannelCredentials the credentials to use when establishing a connection to the S2A. - * @return a {@link ChannelResource} instance that manages a {@link Channel} to the S2A server - * running at {@code s2aAddress}. - */ - public static Resource getChannelResource( - String s2aAddress, Optional s2aChannelCredentials) { - checkNotNull(s2aAddress); - return SHARED_RESOURCE_CHANNELS.computeIfAbsent( - s2aAddress, channelResource -> new ChannelResource(s2aAddress, s2aChannelCredentials)); - } - - /** - * Defines how to create and destroy a {@link Channel} instance that uses shared resources. A - * channel created by {@code ChannelResource} is a plaintext, local channel to the service running - * at {@code targetAddress}. - */ - private static class ChannelResource implements Resource { - private final String targetAddress; - private final Optional channelCredentials; - - public ChannelResource(String targetAddress, Optional channelCredentials) { - this.targetAddress = targetAddress; - this.channelCredentials = channelCredentials; - } - - /** - * Creates a {@code EventLoopHoldingChannel} instance to the service running at {@code - * targetAddress}. This channel uses a dedicated thread pool for its {@code EventLoopGroup} - * instance to avoid blocking. - */ - @Override - public Channel create() { - EventLoopGroup eventLoopGroup = - new NioEventLoopGroup(1, new DefaultThreadFactory("S2A channel pool", true)); - ManagedChannel channel = null; - if (channelCredentials.isPresent()) { - // Create a secure channel. - channel = - NettyChannelBuilder.forTarget(targetAddress, channelCredentials.get()) - .channelType(NioSocketChannel.class) - .directExecutor() - .eventLoopGroup(eventLoopGroup) - .build(); - } else { - // Create a plaintext channel. - channel = - NettyChannelBuilder.forTarget(targetAddress) - .channelType(NioSocketChannel.class) - .directExecutor() - .eventLoopGroup(eventLoopGroup) - .usePlaintext() - .build(); - } - return EventLoopHoldingChannel.create(channel, eventLoopGroup); - } - - /** Destroys a {@code EventLoopHoldingChannel} instance. */ - @Override - public void close(Channel instanceChannel) { - checkNotNull(instanceChannel); - EventLoopHoldingChannel channel = (EventLoopHoldingChannel) instanceChannel; - channel.close(); - } - - @Override - public String toString() { - return "grpc-s2a-channel"; - } - } - - /** - * Manages a channel using a {@link ManagedChannel} instance that belong to the {@code - * EventLoopGroup} thread pool. - */ - @VisibleForTesting - static class EventLoopHoldingChannel extends Channel { - private final ManagedChannel delegate; - private final EventLoopGroup eventLoopGroup; - - static EventLoopHoldingChannel create(ManagedChannel delegate, EventLoopGroup eventLoopGroup) { - checkNotNull(delegate); - checkNotNull(eventLoopGroup); - return new EventLoopHoldingChannel(delegate, eventLoopGroup); - } - - private EventLoopHoldingChannel(ManagedChannel delegate, EventLoopGroup eventLoopGroup) { - this.delegate = delegate; - this.eventLoopGroup = eventLoopGroup; - } - - /** - * Returns the address of the service to which the {@code delegate} channel connects, which is - * typically of the form {@code host:port}. - */ - @Override - public String authority() { - return delegate.authority(); - } - - /** Creates a {@link ClientCall} that invokes the operations in {@link MethodDescriptor}. */ - @Override - public ClientCall newCall( - MethodDescriptor methodDescriptor, CallOptions options) { - return delegate.newCall(methodDescriptor, options); - } - - @SuppressWarnings("FutureReturnValueIgnored") - public void close() { - delegate.shutdownNow(); - boolean isDelegateTerminated; - try { - isDelegateTerminated = - delegate.awaitTermination(DELEGATE_TERMINATION_TIMEOUT.getSeconds(), SECONDS); - } catch (InterruptedException e) { - isDelegateTerminated = false; - } - long quietPeriodSeconds = isDelegateTerminated ? 0 : 1; - eventLoopGroup.shutdownGracefully( - quietPeriodSeconds, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); - } - } - - private S2AHandshakerServiceChannel() {} -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java new file mode 100644 index 00000000000..b1ba88d1886 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024 The gRPC Authors + * + * 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 io.grpc.s2a.internal.channel; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.SECONDS; + +import io.grpc.Channel; +import io.grpc.ChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.internal.SharedResourceHolder.Resource; +import io.grpc.netty.NettyChannelBuilder; +import java.time.Duration; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Provides APIs for managing gRPC channels to an S2A server. Each channel is local and plaintext. + * If credentials are provided, they are used to secure the channel. + * + *

This is done as follows: for an S2A server, provides an implementation of gRPC's {@link + * SharedResourceHolder.Resource} interface called a {@code Resource}. A {@code + * Resource} is a factory for creating gRPC channels to the S2A server at a given address, + * and a channel must be returned to the {@code Resource} when it is no longer needed. + * + *

Typical usage pattern is below: + * + *

{@code
+ * Resource resource = S2AHandshakerServiceChannel.getChannelResource("localhost:1234",
+ * creds);
+ * Channel channel = resource.create();
+ * // Send an RPC over the channel to the S2A server running at localhost:1234.
+ * resource.close(channel);
+ * }
+ */ +@ThreadSafe +public final class S2AHandshakerServiceChannel { + private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); + private static final Logger logger = + Logger.getLogger(S2AHandshakerServiceChannel.class.getName()); + + /** + * Returns a {@link SharedResourceHolder.Resource} instance for managing channels to an S2A server + * running at {@code s2aAddress}. + * + * @param s2aAddress the address of the S2A, typically in the format {@code host:port}. + * @param s2aChannelCredentials the credentials to use when establishing a connection to the S2A. + * @return a {@link ChannelResource} instance that manages a {@link Channel} to the S2A server + * running at {@code s2aAddress}. + */ + public static Resource getChannelResource( + String s2aAddress, ChannelCredentials s2aChannelCredentials) { + checkNotNull(s2aAddress); + return new ChannelResource(s2aAddress, s2aChannelCredentials); + } + + /** + * Defines how to create and destroy a {@link Channel} instance that uses shared resources. A + * channel created by {@code ChannelResource} is a plaintext, local channel to the service running + * at {@code targetAddress}. + */ + private static class ChannelResource implements Resource { + private final String targetAddress; + private final ChannelCredentials channelCredentials; + + public ChannelResource(String targetAddress, ChannelCredentials channelCredentials) { + this.targetAddress = targetAddress; + this.channelCredentials = channelCredentials; + } + + /** + * Creates a {@code ManagedChannel} instance to the service running at {@code + * targetAddress}. + */ + @Override + public Channel create() { + return NettyChannelBuilder.forTarget(targetAddress, channelCredentials) + .directExecutor() + .idleTimeout(5, SECONDS) + .build(); + } + + /** Destroys a {@code ManagedChannel} instance. */ + @Override + public void close(Channel instanceChannel) { + checkNotNull(instanceChannel); + ManagedChannel channel = (ManagedChannel) instanceChannel; + channel.shutdownNow(); + try { + channel.awaitTermination(CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.log(Level.WARNING, "Channel to S2A was not shutdown."); + } + + } + + @Override + public String toString() { + return "grpc-s2a-channel"; + } + } + + private S2AHandshakerServiceChannel() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java similarity index 95% rename from s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java index 1a7f86bda91..d6f1aa70f7c 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import java.io.IOException; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java similarity index 92% rename from s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java index 56d74a9b766..2d089183f91 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import com.google.errorprone.annotations.Immutable; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.tokenmanager.AccessTokenManager; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.tokenmanager.AccessTokenManager; import java.util.Optional; /** Retrieves the authentication mechanism for a given local identity. */ diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java similarity index 63% rename from s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java index 59e3931d9e6..1d88d5a2b55 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java @@ -14,38 +14,13 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; /** Converts proto messages to Netty strings. */ final class ProtoUtil { - /** - * Converts {@link Ciphersuite} to its {@link String} representation. - * - * @param ciphersuite the {@link Ciphersuite} to be converted. - * @return a {@link String} representing the ciphersuite. - * @throws AssertionError if the {@link Ciphersuite} is not one of the supported ciphersuites. - */ - static String convertCiphersuite(Ciphersuite ciphersuite) { - switch (ciphersuite) { - case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; - case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; - case CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: - return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"; - case CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; - case CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; - case CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: - return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"; - default: - throw new AssertionError( - String.format("Ciphersuite %d is not supported.", ciphersuite.getNumber())); - } - } /** * Converts a {@link TLSVersion} object to its {@link String} representation. @@ -54,6 +29,7 @@ static String convertCiphersuite(Ciphersuite ciphersuite) { * @return a {@link String} representation of the TLS version. * @throws AssertionError if the {@code tlsVersion} is not one of the supported TLS versions. */ + @VisibleForTesting static String convertTlsProtocolVersion(TLSVersion tlsVersion) { switch (tlsVersion) { case TLS_VERSION_1_3: diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java similarity index 95% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java index d976308ad22..9b6c244751b 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; /** Exception that denotes a runtime error that was encountered when talking to the S2A server. */ @SuppressWarnings("serial") // This class is never serialized. diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java index c4fed7377ac..0b691248e91 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java index fb6d5761355..c7262f70ef7 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -22,7 +22,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslPrivateKeyMethod; import java.io.IOException; import java.util.Optional; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java similarity index 88% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java index 25d1e325ea8..188faf63435 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -29,15 +29,15 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.errorprone.annotations.ThreadSafe; import io.grpc.Channel; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; +import io.grpc.internal.SharedResourcePool; import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiators; import io.grpc.netty.InternalProtocolNegotiators.ProtocolNegotiationHandler; -import io.grpc.s2a.channel.S2AChannelPool; -import io.grpc.s2a.channel.S2AGrpcChannelPool; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; @@ -68,17 +68,16 @@ public final class S2AProtocolNegotiatorFactory { public static InternalProtocolNegotiator.ClientFactory createClientFactory( @Nullable S2AIdentity localIdentity, ObjectPool s2aChannelPool) { checkNotNull(s2aChannelPool, "S2A channel pool should not be null."); - S2AChannelPool channelPool = S2AGrpcChannelPool.create(s2aChannelPool); - return new S2AClientProtocolNegotiatorFactory(localIdentity, channelPool); + return new S2AClientProtocolNegotiatorFactory(localIdentity, s2aChannelPool); } static final class S2AClientProtocolNegotiatorFactory implements InternalProtocolNegotiator.ClientFactory { private final @Nullable S2AIdentity localIdentity; - private final S2AChannelPool channelPool; + private final ObjectPool channelPool; S2AClientProtocolNegotiatorFactory( - @Nullable S2AIdentity localIdentity, S2AChannelPool channelPool) { + @Nullable S2AIdentity localIdentity, ObjectPool channelPool) { this.localIdentity = localIdentity; this.channelPool = channelPool; } @@ -98,13 +97,14 @@ public int getDefaultPort() { @VisibleForTesting static final class S2AProtocolNegotiator implements ProtocolNegotiator { - private final S2AChannelPool channelPool; + private final ObjectPool channelPool; + private final Channel channel; private final Optional localIdentity; private final ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)); static S2AProtocolNegotiator createForClient( - S2AChannelPool channelPool, @Nullable S2AIdentity localIdentity) { + ObjectPool channelPool, @Nullable S2AIdentity localIdentity) { checkNotNull(channelPool, "Channel pool should not be null."); if (localIdentity == null) { return new S2AProtocolNegotiator(channelPool, Optional.empty()); @@ -121,9 +121,11 @@ static S2AProtocolNegotiator createForClient( return HostAndPort.fromString(authority).getHost(); } - private S2AProtocolNegotiator(S2AChannelPool channelPool, Optional localIdentity) { + private S2AProtocolNegotiator(ObjectPool channelPool, + Optional localIdentity) { this.channelPool = channelPool; this.localIdentity = localIdentity; + this.channel = channelPool.getObject(); } @Override @@ -137,13 +139,13 @@ public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { String hostname = getHostNameFromAuthority(grpcHandler.getAuthority()); checkArgument(!isNullOrEmpty(hostname), "hostname should not be null or empty."); return new S2AProtocolNegotiationHandler( - grpcHandler, channelPool, localIdentity, hostname, service); + grpcHandler, channel, localIdentity, hostname, service); } @Override public void close() { service.shutdown(); - channelPool.close(); + channelPool.returnObject(channel); } } @@ -178,7 +180,7 @@ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { } private static final class S2AProtocolNegotiationHandler extends ProtocolNegotiationHandler { - private final S2AChannelPool channelPool; + private final Channel channel; private final Optional localIdentity; private final String hostname; private final GrpcHttp2ConnectionHandler grpcHandler; @@ -186,7 +188,7 @@ private static final class S2AProtocolNegotiationHandler extends ProtocolNegotia private S2AProtocolNegotiationHandler( GrpcHttp2ConnectionHandler grpcHandler, - S2AChannelPool channelPool, + Channel channel, Optional localIdentity, String hostname, ListeningExecutorService service) { @@ -202,7 +204,7 @@ public void handlerAdded(ChannelHandlerContext ctx) { }, grpcHandler.getNegotiationLogger()); this.grpcHandler = grpcHandler; - this.channelPool = channelPool; + this.channel = channel; this.localIdentity = localIdentity; this.hostname = hostname; checkNotNull(service, "service should not be null."); @@ -215,8 +217,7 @@ protected void handlerAdded0(ChannelHandlerContext ctx) { BufferReadsHandler bufferReads = new BufferReadsHandler(); ctx.pipeline().addBefore(ctx.name(), /* name= */ null, bufferReads); - Channel ch = channelPool.getChannel(); - S2AServiceGrpc.S2AServiceStub stub = S2AServiceGrpc.newStub(ch); + S2AServiceGrpc.S2AServiceStub stub = S2AServiceGrpc.newStub(channel); S2AStub s2aStub = S2AStub.newInstance(stub); ListenableFuture sslContextFuture = @@ -227,7 +228,10 @@ protected void handlerAdded0(ChannelHandlerContext ctx) { @Override public void onSuccess(SslContext sslContext) { ChannelHandler handler = - InternalProtocolNegotiators.tls(sslContext).newHandler(grpcHandler); + InternalProtocolNegotiators.tls( + sslContext, + SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR)) + .newHandler(grpcHandler); // Remove the bufferReads handler and delegate the rest of the handshake to the TLS // handler. diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java similarity index 93% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java index 8249ca59d09..0bfa3b4dac2 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -84,6 +84,7 @@ BlockingQueue getResponses() { * @throws IOException if an unexpected response is received, or if the {@code reader} or {@code * writer} calls their {@code onError} method. */ + @SuppressWarnings("CheckReturnValue") public SessionResp send(SessionReq req) throws IOException, InterruptedException { if (doneWriting && doneReading) { logger.log(Level.INFO, "Stream to the S2A is closed."); @@ -92,9 +93,8 @@ public SessionResp send(SessionReq req) throws IOException, InterruptedException createWriterIfNull(); if (!responses.isEmpty()) { IOException exception = null; - SessionResp resp = null; try { - resp = responses.take().getResultOrThrow(); + responses.take().getResultOrThrow(); } catch (IOException e) { exception = e; } @@ -102,16 +102,16 @@ public SessionResp send(SessionReq req) throws IOException, InterruptedException if (exception != null) { throw new IOException( "Received an unexpected response from a host at the S2A's address. The S2A might be" - + " unavailable." - + exception.getMessage()); + + " unavailable.", exception); + } else { + throw new IOException("Received an unexpected response from a host at the S2A's address."); } - return resp; } try { writer.onNext(req); } catch (RuntimeException e) { writer.onError(e); - responses.offer(Result.createWithThrowable(e)); + responses.add(Result.createWithThrowable(e)); } try { return responses.take().getResultOrThrow(); @@ -159,7 +159,7 @@ private class Reader implements StreamObserver { @Override public void onNext(SessionResp resp) { verify(!doneReading); - responses.offer(Result.createWithResponse(resp)); + responses.add(Result.createWithResponse(resp)); } /** @@ -169,7 +169,7 @@ public void onNext(SessionResp resp) { */ @Override public void onError(Throwable t) { - responses.offer(Result.createWithThrowable(t)); + responses.add(Result.createWithThrowable(t)); } /** @@ -180,7 +180,7 @@ public void onError(Throwable t) { public void onCompleted() { logger.log(Level.INFO, "Reading from the S2A is complete."); doneReading = true; - responses.offer( + responses.add( Result.createWithThrowable( new ConnectionClosedException("Reading from the S2A is complete."))); } diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java similarity index 95% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java index fb113bb29cc..2f7e5750f88 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.ValidatePeerCertificateChainReq.VerificationMode; import java.io.IOException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; @@ -121,6 +121,9 @@ private void checkPeerTrusted(X509Certificate[] chain, boolean isCheckingClientC try { resp = stub.send(reqBuilder.build()); } catch (IOException | InterruptedException e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } throw new CertificateException("Failed to send request to S2A.", e); } if (resp.hasStatus() && resp.getStatus().getCode() != 0) { diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java index 1ac5887ebc4..72ace2c7885 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableSet; import io.grpc.netty.GrpcSslContexts; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslContextOption; import io.netty.handler.ssl.OpenSslSessionContext; import io.netty.handler.ssl.OpenSslX509KeyManagerFactory; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java similarity index 66% rename from s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java index 94549d11c87..65fca46bbb2 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; -import io.grpc.s2a.handshaker.S2AIdentity; -import java.lang.reflect.Method; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.util.Optional; import javax.annotation.concurrent.ThreadSafe; @@ -27,21 +26,10 @@ public final class AccessTokenManager { private final TokenFetcher tokenFetcher; /** Creates an {@code AccessTokenManager} based on the environment where the application runs. */ - @SuppressWarnings("RethrowReflectiveOperationExceptionAsLinkageError") public static Optional create() { - Optional tokenFetcher; - try { - Class singleTokenFetcherClass = - Class.forName("io.grpc.s2a.handshaker.tokenmanager.SingleTokenFetcher"); - Method createTokenFetcher = singleTokenFetcherClass.getMethod("create"); - tokenFetcher = (Optional) createTokenFetcher.invoke(null); - } catch (ClassNotFoundException e) { - tokenFetcher = Optional.empty(); - } catch (ReflectiveOperationException e) { - throw new AssertionError(e); - } + Optional tokenFetcher = SingleTokenFetcher.create(); return tokenFetcher.isPresent() - ? Optional.of(new AccessTokenManager((TokenFetcher) tokenFetcher.get())) + ? Optional.of(new AccessTokenManager(tokenFetcher.get())) : Optional.empty(); } diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java similarity index 94% rename from s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java index c3dffd2b715..a5402af9db2 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; import com.google.common.annotations.VisibleForTesting; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.util.Optional; /** Fetches a single access token via an environment variable. */ diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java similarity index 89% rename from s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java index 9eeddaad844..6827f095afe 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; /** Fetches tokens used to authenticate to S2A. */ interface TokenFetcher { diff --git a/s2a/src/main/proto/grpc/gcp/s2a/common.proto b/s2a/src/main/proto/grpc/gcp/s2a/common.proto index 749739553dd..1b999234669 100644 --- a/s2a/src/main/proto/grpc/gcp/s2a/common.proto +++ b/s2a/src/main/proto/grpc/gcp/s2a/common.proto @@ -21,7 +21,7 @@ package grpc.gcp.s2a; option java_multiple_files = true; option java_outer_classname = "CommonProto"; -option java_package = "io.grpc.s2a.handshaker"; +option java_package = "io.grpc.s2a.internal.handshaker"; // The TLS 1.0-1.2 ciphersuites that the application can negotiate when using // S2A. diff --git a/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto b/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto index 8a85e348c24..b3f153943db 100644 --- a/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto +++ b/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto @@ -24,7 +24,7 @@ import "grpc/gcp/s2a/s2a_context.proto"; option java_multiple_files = true; option java_outer_classname = "S2AProto"; -option java_package = "io.grpc.s2a.handshaker"; +option java_package = "io.grpc.s2a.internal.handshaker"; enum SignatureAlgorithm { S2A_SSL_SIGN_UNSPECIFIED = 0; diff --git a/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto b/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto index edaeaf22669..745b4d267d6 100644 --- a/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto +++ b/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto @@ -23,7 +23,7 @@ import "grpc/gcp/s2a/common.proto"; option java_multiple_files = true; option java_outer_classname = "S2AContextProto"; -option java_package = "io.grpc.s2a.handshaker"; +option java_package = "io.grpc.s2a.internal.handshaker"; message S2AContext { // The SPIFFE ID from the peer leaf certificate, if present. diff --git a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java deleted file mode 100644 index 5ccc522292e..00000000000 --- a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * 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 io.grpc.s2a; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class MtlsToS2AChannelCredentialsTest { - @Test - public void createBuilder_nullAddress_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ null, - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void createBuilder_nullPrivateKeyPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ null, - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void createBuilder_nullCertChainPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ null, - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void createBuilder_nullTrustBundlePath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ null)); - } - - @Test - public void createBuilder_emptyAddress_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void createBuilder_emptyPrivateKeyPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void createBuilder_emptyCertChainPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void createBuilder_emptyTrustBundlePath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "")); - } - - @Test - public void build_s2AChannelCredentials_success() throws Exception { - assertThat( - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem") - .build()) - .isInstanceOf(S2AChannelCredentials.Builder.class); - } -} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java index a6133ed0af8..fd5bfd654f3 100644 --- a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java +++ b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThrows; import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; import io.grpc.TlsChannelCredentials; import java.io.File; import org.junit.Test; @@ -30,40 +31,51 @@ @RunWith(JUnit4.class) public final class S2AChannelCredentialsTest { @Test - public void createBuilder_nullArgument_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.createBuilder(null)); + public void newBuilder_nullAddress_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(null, + InsecureChannelCredentials.create())); } @Test - public void createBuilder_emptyAddress_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.createBuilder("")); + public void newBuilder_emptyAddress_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder("", + InsecureChannelCredentials.create())); + } + + @Test + public void newBuilder_nullChannelCreds_throwsException() throws Exception { + assertThrows(NullPointerException.class, () -> S2AChannelCredentials + .newBuilder("s2a_address", null)); } @Test public void setLocalSpiffeId_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalSpiffeId(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalSpiffeId(null)); } @Test public void setLocalHostname_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalHostname(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalHostname(null)); } @Test public void setLocalUid_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalUid(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalUid(null)); } @Test public void build_withLocalSpiffeId_succeeds() throws Exception { assertThat( - S2AChannelCredentials.createBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address", InsecureChannelCredentials.create()) .setLocalSpiffeId("spiffe://test") .build()) .isNotNull(); @@ -72,7 +84,7 @@ public void build_withLocalSpiffeId_succeeds() throws Exception { @Test public void build_withLocalHostname_succeeds() throws Exception { assertThat( - S2AChannelCredentials.createBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address", InsecureChannelCredentials.create()) .setLocalHostname("local_hostname") .build()) .isNotNull(); @@ -80,33 +92,47 @@ public void build_withLocalHostname_succeeds() throws Exception { @Test public void build_withLocalUid_succeeds() throws Exception { - assertThat(S2AChannelCredentials.createBuilder("s2a_address").setLocalUid("local_uid").build()) + assertThat(S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalUid("local_uid").build()) .isNotNull(); } @Test public void build_withNoLocalIdentity_succeeds() throws Exception { - assertThat(S2AChannelCredentials.createBuilder("s2a_address").build()) + assertThat(S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).build()) .isNotNull(); } - + @Test - public void build_withTlsChannelCredentials_succeeds() throws Exception { + public void build_withUseMtlsToS2ANoLocalIdentity_success() throws Exception { + ChannelCredentials s2aChannelCredentials = getTlsChannelCredentials(); assertThat( - S2AChannelCredentials.createBuilder("s2a_address") - .setLocalSpiffeId("spiffe://test") - .setS2AChannelCredentials(getTlsChannelCredentials()) + S2AChannelCredentials.newBuilder("s2a_address", s2aChannelCredentials) + .build()) + .isNotNull(); + } + + @Test + public void build_withUseMtlsToS2AWithLocalUid_success() throws Exception { + ChannelCredentials s2aChannelCredentials = getTlsChannelCredentials(); + assertThat( + S2AChannelCredentials.newBuilder("s2a_address", s2aChannelCredentials) + .setLocalUid("local_uid") .build()) .isNotNull(); } private static ChannelCredentials getTlsChannelCredentials() throws Exception { - File clientCert = new File("src/test/resources/client_cert.pem"); - File clientKey = new File("src/test/resources/client_key.pem"); - File rootCert = new File("src/test/resources/root_cert.pem"); + String privateKeyPath = "src/test/resources/client_key.pem"; + String certChainPath = "src/test/resources/client_cert.pem"; + String trustBundlePath = "src/test/resources/root_cert.pem"; + File privateKeyFile = new File(privateKeyPath); + File certChainFile = new File(certChainPath); + File trustBundleFile = new File(trustBundlePath); return TlsChannelCredentials.newBuilder() - .keyManager(clientCert, clientKey) - .trustManager(rootCert) - .build(); + .keyManager(certChainFile, privateKeyFile) + .trustManager(trustBundleFile) + .build(); } } \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java b/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java deleted file mode 100644 index 260129f8f56..00000000000 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * 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 io.grpc.s2a.channel; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.mock; - -import io.grpc.Channel; -import io.grpc.internal.ObjectPool; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link S2AGrpcChannelPool}. */ -@RunWith(JUnit4.class) -public final class S2AGrpcChannelPoolTest { - @Test - public void getChannel_success() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); - - Channel channel = s2aChannelPool.getChannel(); - - assertThat(channel).isNotNull(); - assertThat(fakeChannelPool.isChannelCached()).isTrue(); - assertThat(s2aChannelPool.getChannel()).isEqualTo(channel); - } - - @Test - public void returnToPool_success() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); - - s2aChannelPool.returnToPool(s2aChannelPool.getChannel()); - - assertThat(fakeChannelPool.isChannelCached()).isFalse(); - } - - @Test - public void returnToPool_channelStillCachedBecauseMultipleChannelsRetrieved() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); - - s2aChannelPool.getChannel(); - s2aChannelPool.returnToPool(s2aChannelPool.getChannel()); - - assertThat(fakeChannelPool.isChannelCached()).isTrue(); - } - - @Test - public void returnToPool_failureBecauseChannelWasNotFromPool() throws Exception { - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(new FakeChannelPool()); - - IllegalArgumentException expected = - assertThrows( - IllegalArgumentException.class, - () -> s2aChannelPool.returnToPool(mock(Channel.class))); - assertThat(expected) - .hasMessageThat() - .isEqualTo( - "Cannot return the channel to channel pool because the channel was not obtained from" - + " channel pool."); - } - - @Test - public void close_success() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - try (S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool)) { - s2aChannelPool.getChannel(); - } - - assertThat(fakeChannelPool.isChannelCached()).isFalse(); - } - - @Test - public void close_poolIsUnusable() throws Exception { - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(new FakeChannelPool()); - s2aChannelPool.close(); - - IllegalStateException expected = - assertThrows(IllegalStateException.class, s2aChannelPool::getChannel); - - assertThat(expected).hasMessageThat().isEqualTo("Channel pool is not open."); - } - - private static class FakeChannelPool implements ObjectPool { - private final Channel mockChannel = mock(Channel.class); - private @Nullable Channel cachedChannel = null; - - @Override - public Channel getObject() { - if (cachedChannel == null) { - cachedChannel = mockChannel; - } - return cachedChannel; - } - - @Override - public Channel returnObject(Object object) { - assertThat(object).isSameInstanceAs(mockChannel); - cachedChannel = null; - return null; - } - - public boolean isChannelCached() { - return (cachedChannel != null); - } - } -} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java similarity index 59% rename from s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java index 57288be1b6f..16409721ff5 100644 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java @@ -14,22 +14,16 @@ * limitations under the License. */ -package io.grpc.s2a.channel; +package io.grpc.s2a.internal.channel; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ChannelCredentials; -import io.grpc.ClientCall; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.MethodDescriptor; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerCredentials; @@ -39,17 +33,12 @@ import io.grpc.benchmarks.Utils; import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyServerBuilder; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel.EventLoopHoldingChannel; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; -import io.netty.channel.EventLoopGroup; import java.io.File; -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; @@ -60,8 +49,6 @@ @RunWith(JUnit4.class) public final class S2AHandshakerServiceChannelTest { @ClassRule public static final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); - private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); - private final EventLoopGroup mockEventLoopGroup = mock(EventLoopGroup.class); private Server mtlsServer; private Server plaintextServer; @@ -82,7 +69,7 @@ public void getChannelResource_success() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); assertThat(resource.toString()).isEqualTo("grpc-s2a-channel"); } @@ -97,31 +84,31 @@ public void getChannelResource_mtlsSuccess() throws Exception { /** * Creates two {@code Resoure}s for the same target address and verifies that they are - * equal. + * distinct. */ @Test - public void getChannelResource_twoEqualChannels() { + public void getChannelResource_twoUnEqualChannels() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Resource resourceTwo = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); - assertThat(resource).isEqualTo(resourceTwo); + InsecureChannelCredentials.create()); + assertThat(resource).isNotEqualTo(resourceTwo); } - /** Same as getChannelResource_twoEqualChannels, but use mTLS. */ + /** Same as getChannelResource_twoUnEqualChannels, but use mTLS. */ @Test - public void getChannelResource_mtlsTwoEqualChannels() throws Exception { + public void getChannelResource_mtlsTwoUnEqualChannels() throws Exception { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); Resource resourceTwo = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); - assertThat(resource).isEqualTo(resourceTwo); + assertThat(resource).isNotEqualTo(resourceTwo); } /** @@ -133,10 +120,10 @@ public void getChannelResource_twoDistinctChannels() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Resource resourceTwo = S2AHandshakerServiceChannel.getChannelResource( - "localhost:" + Utils.pickUnusedPort(), /* s2aChannelCredentials= */ Optional.empty()); + "localhost:" + Utils.pickUnusedPort(), InsecureChannelCredentials.create()); assertThat(resourceTwo).isNotEqualTo(resource); } @@ -161,7 +148,7 @@ public void close_success() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Channel channel = resource.create(); resource.close(channel); StatusRuntimeException expected = @@ -191,76 +178,7 @@ public void close_mtlsSuccess() throws Exception { } /** - * Verifies that an {@code EventLoopHoldingChannel}'s {@code newCall} method can be used to - * perform a simple RPC. - */ - @Test - public void newCall_performSimpleRpcSuccess() { - Resource resource = - S2AHandshakerServiceChannel.getChannelResource( - "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); - Channel channel = resource.create(); - assertThat(channel).isInstanceOf(EventLoopHoldingChannel.class); - assertThat( - SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) - .isEqualToDefaultInstance(); - } - - /** Same as newCall_performSimpleRpcSuccess, but use mTLS. */ - @Test - public void newCall_mtlsPerformSimpleRpcSuccess() throws Exception { - Resource resource = - S2AHandshakerServiceChannel.getChannelResource( - "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); - Channel channel = resource.create(); - assertThat(channel).isInstanceOf(EventLoopHoldingChannel.class); - assertThat( - SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) - .isEqualToDefaultInstance(); - } - - /** Creates a {@code EventLoopHoldingChannel} instance and verifies its authority. */ - @Test - public void authority_success() throws Exception { - ManagedChannel channel = new FakeManagedChannel(true); - EventLoopHoldingChannel eventLoopHoldingChannel = - EventLoopHoldingChannel.create(channel, mockEventLoopGroup); - assertThat(eventLoopHoldingChannel.authority()).isEqualTo("FakeManagedChannel"); - } - - /** - * Creates and closes a {@code EventLoopHoldingChannel} when its {@code ManagedChannel} terminates - * successfully. - */ - @Test - public void close_withDelegateTerminatedSuccess() throws Exception { - ManagedChannel channel = new FakeManagedChannel(true); - EventLoopHoldingChannel eventLoopHoldingChannel = - EventLoopHoldingChannel.create(channel, mockEventLoopGroup); - eventLoopHoldingChannel.close(); - assertThat(channel.isShutdown()).isTrue(); - verify(mockEventLoopGroup, times(1)) - .shutdownGracefully(0, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); - } - - /** - * Creates and closes a {@code EventLoopHoldingChannel} when its {@code ManagedChannel} does not - * terminate successfully. - */ - @Test - public void close_withDelegateTerminatedFailure() throws Exception { - ManagedChannel channel = new FakeManagedChannel(false); - EventLoopHoldingChannel eventLoopHoldingChannel = - EventLoopHoldingChannel.create(channel, mockEventLoopGroup); - eventLoopHoldingChannel.close(); - assertThat(channel.isShutdown()).isTrue(); - verify(mockEventLoopGroup, times(1)) - .shutdownGracefully(1, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); - } - - /** - * Creates and closes a {@code EventLoopHoldingChannel}, creates a new channel from the same + * Creates and closes a {@code ManagedChannel}, creates a new channel from the same * resource, and verifies that this second channel is useable. */ @Test @@ -268,12 +186,12 @@ public void create_succeedsAfterCloseIsCalledOnce() throws Exception { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Channel channelOne = resource.create(); resource.close(channelOne); Channel channelTwo = resource.create(); - assertThat(channelTwo).isInstanceOf(EventLoopHoldingChannel.class); + assertThat(channelTwo).isInstanceOf(ManagedChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channelTwo) .unaryRpc(SimpleRequest.getDefaultInstance())) @@ -291,7 +209,7 @@ public void create_mtlsSucceedsAfterCloseIsCalledOnce() throws Exception { resource.close(channelOne); Channel channelTwo = resource.create(); - assertThat(channelTwo).isInstanceOf(EventLoopHoldingChannel.class); + assertThat(channelTwo).isInstanceOf(ManagedChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channelTwo) .unaryRpc(SimpleRequest.getDefaultInstance())) @@ -320,15 +238,14 @@ private static Server createPlaintextServer() { ServerBuilder.forPort(Utils.pickUnusedPort()).addService(service).build()); } - private static Optional getTlsChannelCredentials() throws Exception { + private static ChannelCredentials getTlsChannelCredentials() throws Exception { File clientCert = new File("src/test/resources/client_cert.pem"); File clientKey = new File("src/test/resources/client_key.pem"); File rootCert = new File("src/test/resources/root_cert.pem"); - return Optional.of( - TlsChannelCredentials.newBuilder() + return TlsChannelCredentials.newBuilder() .keyManager(clientCert, clientKey) .trustManager(rootCert) - .build()); + .build(); } private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { @@ -338,53 +255,4 @@ public void unaryRpc(SimpleRequest request, StreamObserver strea streamObserver.onCompleted(); } } - - private static class FakeManagedChannel extends ManagedChannel { - private final boolean isDelegateTerminatedSuccess; - private boolean isShutdown = false; - - FakeManagedChannel(boolean isDelegateTerminatedSuccess) { - this.isDelegateTerminatedSuccess = isDelegateTerminatedSuccess; - } - - @Override - public String authority() { - return "FakeManagedChannel"; - } - - @Override - public ClientCall newCall( - MethodDescriptor methodDescriptor, CallOptions options) { - throw new UnsupportedOperationException("This method should not be called."); - } - - @Override - public ManagedChannel shutdown() { - throw new UnsupportedOperationException("This method should not be called."); - } - - @Override - public boolean isShutdown() { - return isShutdown; - } - - @Override - public boolean isTerminated() { - throw new UnsupportedOperationException("This method should not be called."); - } - - @Override - public ManagedChannel shutdownNow() { - isShutdown = true; - return null; - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - if (isDelegateTerminatedSuccess) { - return true; - } - throw new InterruptedException("Await termination was interrupted."); - } - } } diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java similarity index 86% rename from s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java index 66f636ada22..2d19dd122ec 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import io.grpc.stub.StreamObserver; +import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.logging.Logger; @@ -27,7 +28,7 @@ public final class FakeS2AServer extends S2AServiceGrpc.S2AServiceImplBase { private final FakeWriter writer; - public FakeS2AServer() throws InvalidKeySpecException, NoSuchAlgorithmException { + public FakeS2AServer() throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { this.writer = new FakeWriter(); this.writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS).initializePrivateKey(); } @@ -38,7 +39,11 @@ public StreamObserver setUpSession(StreamObserver respo @Override public void onNext(SessionReq req) { logger.info("Received a request from client."); - responseObserver.onNext(writer.handleResponse(req)); + try { + responseObserver.onNext(writer.handleResponse(req)); + } catch (IOException e) { + responseObserver.onError(e); + } } @Override diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServerTest.java similarity index 91% rename from s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServerTest.java index e200d119867..e61f8eea1a1 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; @@ -27,8 +27,11 @@ import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.benchmarks.Utils; -import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; +import io.grpc.s2a.internal.handshaker.ValidatePeerCertificateChainReq.VerificationMode; import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -45,9 +48,7 @@ public final class FakeS2AServerTest { private static final Logger logger = Logger.getLogger(FakeS2AServerTest.class.getName()); private static final ImmutableList FAKE_CERT_DER_CHAIN = - ImmutableList.of( - ByteString.copyFrom( - new byte[] {'f', 'a', 'k', 'e', '-', 'd', 'e', 'r', '-', 'c', 'h', 'a', 'i', 'n'})); + ImmutableList.of(ByteString.copyFrom("fake-der-chain".getBytes(StandardCharsets.US_ASCII))); private int port; private String serverAddress; private SessionResp response = null; @@ -68,7 +69,7 @@ public void tearDown() { @Test public void callS2AServerOnce_getTlsConfiguration_returnsValidResult() - throws InterruptedException { + throws InterruptedException, IOException { ExecutorService executor = Executors.newSingleThreadExecutor(); logger.info("Client connecting to: " + serverAddress); ManagedChannel channel = @@ -122,9 +123,12 @@ public void onCompleted() {} GetTlsConfigurationResp.newBuilder() .setClientTlsConfiguration( GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(FakeWriter.LEAF_CERT) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) .addCiphersuites( diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java similarity index 58% rename from s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java index 45961b81b7b..4455e77b1e2 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java @@ -14,23 +14,27 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; -import static io.grpc.s2a.handshaker.TLSVersion.TLS_VERSION_1_2; -import static io.grpc.s2a.handshaker.TLSVersion.TLS_VERSION_1_3; +import static io.grpc.s2a.internal.handshaker.TLSVersion.TLS_VERSION_1_2; +import static io.grpc.s2a.internal.handshaker.TLSVersion.TLS_VERSION_1_3; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.ByteString; import io.grpc.stub.StreamObserver; +import io.grpc.util.CertificateUtils; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; -import java.security.KeyFactory; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; /** A fake Writer Class to mock the behavior of S2A server. */ final class FakeWriter implements StreamObserver { @@ -50,59 +54,14 @@ enum VerificationResult { FAILURE } - public static final String LEAF_CERT = - "-----BEGIN CERTIFICATE-----\n" - + "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" - + "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" - + "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" - + "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" - + "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n" - + "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n" - + "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n" - + "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n" - + "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n" - + "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n" - + "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n" - + "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n" - + "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n" - + "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n" - + "-----END CERTIFICATE-----"; - public static final String INTERMEDIATE_CERT_2 = - "-----BEGIN CERTIFICATE-----\n" - + "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n" - + "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n" - + "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n" - + "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n" - + "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n" - + "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n" - + "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" - + "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n" - + "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n" - + "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n" - + "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n" - + "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n" - + "gjIY71MO\n" - + "-----END CERTIFICATE-----"; - public static final String INTERMEDIATE_CERT_1 = - "-----BEGIN CERTIFICATE-----\n" - + "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n" - + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" - + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n" - + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n" - + "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n" - + "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n" - + "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n" - + "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n" - + "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n" - + "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n" - + "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n" - + "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n" - + "-----END CERTIFICATE-----"; - - private static final String PRIVATE_KEY = - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf" - + "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t" - + "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id"; + public static final File leafCertFile = + new File("src/test/resources/leaf_cert_ec.pem"); + public static final File cert2File = + new File("src/test/resources/int_cert2_ec.pem"); + public static final File cert1File = + new File("src/test/resources/int_cert1_ec.pem"); + public static final File keyFile = + new File("src/test/resources/leaf_key_ec.pem"); private static final ImmutableMap ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER = ImmutableMap.of( @@ -145,10 +104,14 @@ FakeWriter setFailureReason(String failureReason) { } @CanIgnoreReturnValue - FakeWriter initializePrivateKey() throws InvalidKeySpecException, NoSuchAlgorithmException { - privateKey = - KeyFactory.getInstance("EC") - .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY))); + FakeWriter initializePrivateKey() throws InvalidKeySpecException, NoSuchAlgorithmException, + IOException, FileNotFoundException, UnsupportedEncodingException { + FileInputStream keyInputStream = new FileInputStream(keyFile); + try { + privateKey = CertificateUtils.getPrivateKey(keyInputStream); + } finally { + keyInputStream.close(); + } return this; } @@ -167,24 +130,32 @@ void sendIoError() { } void sendGetTlsConfigResp() { - reader.onNext( - SessionResp.newBuilder() - .setGetTlsConfigurationResp( - GetTlsConfigurationResp.newBuilder() - .setClientTlsConfiguration( - GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(LEAF_CERT) - .addCertificateChain(INTERMEDIATE_CERT_2) - .addCertificateChain(INTERMEDIATE_CERT_1) - .setMinTlsVersion(TLS_VERSION_1_3) - .setMaxTlsVersion(TLS_VERSION_1_3) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) - .build()); + try { + reader.onNext( + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite + .CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build()); + } catch (IOException e) { + reader.onError(e); + } } boolean isFakeWriterClosed() { @@ -195,7 +166,11 @@ boolean isFakeWriterClosed() { public void onNext(SessionReq sessionReq) { switch (behavior) { case OK_STATUS: - reader.onNext(handleResponse(sessionReq)); + try { + reader.onNext(handleResponse(sessionReq)); + } catch (IOException e) { + reader.onError(e); + } break; case EMPTY_RESPONSE: reader.onNext(SessionResp.getDefaultInstance()); @@ -216,25 +191,36 @@ public void onNext(SessionReq sessionReq) { reader.onCompleted(); break; case BAD_TLS_VERSION_RESPONSE: - reader.onNext( - SessionResp.newBuilder() - .setGetTlsConfigurationResp( - GetTlsConfigurationResp.newBuilder() - .setClientTlsConfiguration( - GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(LEAF_CERT) - .addCertificateChain(INTERMEDIATE_CERT_2) - .addCertificateChain(INTERMEDIATE_CERT_1) - .setMinTlsVersion(TLS_VERSION_1_3) - .setMaxTlsVersion(TLS_VERSION_1_2))) - .build()); + try { + reader.onNext( + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_2))) + .build()); + } catch (IOException e) { + reader.onError(e); + } break; default: - reader.onNext(handleResponse(sessionReq)); + try { + reader.onNext(handleResponse(sessionReq)); + } catch (IOException e) { + reader.onError(e); + } } } - SessionResp handleResponse(SessionReq sessionReq) { + SessionResp handleResponse(SessionReq sessionReq) throws IOException { if (sessionReq.hasGetTlsConfigurationReq()) { return handleGetTlsConfigurationReq(sessionReq.getGetTlsConfigurationReq()); } @@ -253,7 +239,8 @@ SessionResp handleResponse(SessionReq sessionReq) { .build(); } - private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) { + private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) + throws IOException { if (!req.getConnectionSide().equals(ConnectionSide.CONNECTION_SIDE_CLIENT)) { return SessionResp.newBuilder() .setStatus( @@ -267,9 +254,12 @@ private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) { GetTlsConfigurationResp.newBuilder() .setClientTlsConfiguration( GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(LEAF_CERT) - .addCertificateChain(INTERMEDIATE_CERT_2) - .addCertificateChain(INTERMEDIATE_CERT_1) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) .setMinTlsVersion(TLS_VERSION_1_3) .setMaxTlsVersion(TLS_VERSION_1_3) .addCiphersuites( diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanismsTest.java similarity index 92% rename from s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanismsTest.java index 884e1ec88eb..4c00b0746fc 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanismsTest.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import com.google.common.truth.Expect; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.tokenmanager.SingleTokenFetcher; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.tokenmanager.SingleTokenFetcher; import java.util.Optional; import org.junit.BeforeClass; import org.junit.Rule; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/IntegrationTest.java similarity index 57% rename from s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/IntegrationTest.java index 19dda7a19e4..e1ad3d278c3 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/IntegrationTest.java @@ -14,25 +14,25 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; import io.grpc.ChannelCredentials; import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerCredentials; +import io.grpc.TlsChannelCredentials; import io.grpc.TlsServerCredentials; import io.grpc.benchmarks.Utils; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyServerBuilder; -import io.grpc.s2a.MtlsToS2AChannelCredentials; import io.grpc.s2a.S2AChannelCredentials; -import io.grpc.s2a.handshaker.FakeS2AServer; +import io.grpc.s2a.internal.handshaker.FakeS2AServer; import io.grpc.stub.StreamObserver; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; @@ -42,7 +42,6 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; -import java.io.ByteArrayInputStream; import java.io.File; import java.util.concurrent.FutureTask; import java.util.logging.Logger; @@ -58,72 +57,12 @@ public final class IntegrationTest { private static final Logger logger = Logger.getLogger(FakeS2AServer.class.getName()); - private static final String CERT_CHAIN = - "-----BEGIN CERTIFICATE-----\n" - + "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" - + "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" - + "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" - + "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" - + "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n" - + "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n" - + "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n" - + "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n" - + "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n" - + "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n" - + "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n" - + "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n" - + "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n" - + "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n" - + "-----END CERTIFICATE-----\n" - + "-----BEGIN CERTIFICATE-----\n" - + "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n" - + "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n" - + "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n" - + "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n" - + "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n" - + "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n" - + "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" - + "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n" - + "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n" - + "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n" - + "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n" - + "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n" - + "gjIY71MO\n" - + "-----END CERTIFICATE-----\n" - + "-----BEGIN CERTIFICATE-----\n" - + "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n" - + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" - + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n" - + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n" - + "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n" - + "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n" - + "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n" - + "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n" - + "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n" - + "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n" - + "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n" - + "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n" - + "-----END CERTIFICATE-----"; - private static final String ROOT_PEM = - "-----BEGIN CERTIFICATE-----\n" - + "MIIBtTCCAVqgAwIBAgIUbAe+8OocndQXRBCElLBxBSdfdV8wCgYIKoZIzj0EAwIw\n" - + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" - + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDcxFzAV\n" - + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMQ0wCwYDVQQLDARyb290MQ0wCwYDVQQDDAQx\n" - + "MjM0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaMY2tBW5r1t0+vhayz0ZoGMF\n" - + "boX/ZmmCmIh0iTWg4madvwNOh74CMVVvDUlXZcuVqZ3vVIX/a7PTFVqUwQlKW6NC\n" - + "MEAwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMX+\n" - + "vebuj/lYfYEC23IA8HoIW0HsMAoGCCqGSM49BAMCA0kAMEYCIQDETd27nsUTXKWY\n" - + "CiOno78O09gK95NoTkPU5e2chJYMqAIhALYFAyh7PU5xgFQsN9hiqgsHUc5/pmBG\n" - + "BGjJ1iz8rWGJ\n" - + "-----END CERTIFICATE-----"; - private static final String PRIVATE_KEY = - "-----BEGIN PRIVATE KEY-----\n" - + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf\n" - + "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t\n" - + "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id\n" - + "-----END PRIVATE KEY-----"; - + public static final File privateKeyFile = + new File("src/test/resources/leaf_key_ec.pem"); + public static final File rootCertFile = + new File("src/test/resources/root_cert_ec.pem"); + public static final File certChainFile = + new File("src/test/resources/cert_chain_ec.pem"); private String s2aAddress; private Server s2aServer; private String s2aDelayAddress; @@ -186,7 +125,8 @@ public void tearDown() throws Exception { @Test public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { ChannelCredentials credentials = - S2AChannelCredentials.createBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build(); + S2AChannelCredentials.newBuilder(s2aAddress, InsecureChannelCredentials.create()) + .setLocalSpiffeId("test-spiffe-id").build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -194,7 +134,8 @@ public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { @Test public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throws Exception { - ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aAddress).build(); + ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aAddress, + InsecureChannelCredentials.create()).build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -202,15 +143,22 @@ public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throw @Test public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Exception { + String privateKeyPath = "src/test/resources/client_key.pem"; + String certChainPath = "src/test/resources/client_cert.pem"; + String trustBundlePath = "src/test/resources/root_cert.pem"; + File privateKeyFile = new File(privateKeyPath); + File certChainFile = new File(certChainPath); + File trustBundleFile = new File(trustBundlePath); + ChannelCredentials s2aChannelCredentials = + TlsChannelCredentials.newBuilder() + .keyManager(certChainFile, privateKeyFile) + .trustManager(trustBundleFile) + .build(); + ChannelCredentials credentials = - MtlsToS2AChannelCredentials.createBuilder( - /* s2aAddress= */ mtlsS2AAddress, - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem") - .build() - .setLocalSpiffeId("test-spiffe-id") - .build(); + S2AChannelCredentials.newBuilder(mtlsS2AAddress, s2aChannelCredentials) + .setLocalSpiffeId("test-spiffe-id") + .build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -218,7 +166,8 @@ public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Excepti @Test public void clientCommunicateUsingS2ACredentials_s2AdelayStart_succeeds() throws Exception { - ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aDelayAddress).build(); + ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aDelayAddress, + InsecureChannelCredentials.create()).build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); FutureTask rpc = new FutureTask<>(() -> doUnaryRpc(channel)); @@ -252,13 +201,11 @@ public static boolean doUnaryRpc(ManagedChannel channel) throws InterruptedExcep private static SslContext buildSslContext() throws SSLException { SslContextBuilder sslServerContextBuilder = - SslContextBuilder.forServer( - new ByteArrayInputStream(CERT_CHAIN.getBytes(UTF_8)), - new ByteArrayInputStream(PRIVATE_KEY.getBytes(UTF_8))); + SslContextBuilder.forServer(certChainFile, privateKeyFile); SslContext sslServerContext = GrpcSslContexts.configure(sslServerContextBuilder, SslProvider.OPENSSL) .protocols("TLSv1.3", "TLSv1.2") - .trustManager(new ByteArrayInputStream(ROOT_PEM.getBytes(UTF_8))) + .trustManager(rootCertFile) .clientAuth(ClientAuth.REQUIRE) .build(); diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/ProtoUtilTest.java similarity index 65% rename from s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/ProtoUtilTest.java index 6d134b43f7a..b685d0bc755 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/ProtoUtilTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static org.junit.Assert.assertThrows; @@ -30,47 +30,6 @@ public final class ProtoUtilTest { @Rule public final Expect expect = Expect.create(); - @Test - public void convertCiphersuite_success() { - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)) - .isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)) - .isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)) - .isEqualTo("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"); - expect - .that( - ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256)) - .isEqualTo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); - expect - .that( - ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384)) - .isEqualTo("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"); - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)) - .isEqualTo("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"); - } - - @Test - public void convertCiphersuite_withUnspecifiedCiphersuite_fails() { - AssertionError expected = - assertThrows( - AssertionError.class, - () -> ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_UNSPECIFIED)); - expect.that(expected).hasMessageThat().isEqualTo("Ciphersuite 0 is not supported."); - } - @Test public void convertTlsProtocolVersion_success() { expect diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethodTest.java similarity index 97% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethodTest.java index 8252aa245d7..c885783be99 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethodTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -26,10 +26,12 @@ import com.google.common.truth.Expect; import com.google.protobuf.ByteString; import io.grpc.netty.GrpcSslContexts; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslPrivateKeyMethod; import io.netty.handler.ssl.SslContextBuilder; import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateFactory; @@ -61,7 +63,8 @@ private static PublicKey extractPublicKeyFromPem(String pem) throws Exception { private static boolean verifySignature( byte[] dataToSign, byte[] signature, String signatureAlgorithm) throws Exception { Signature sig = Signature.getInstance(signatureAlgorithm); - sig.initVerify(extractPublicKeyFromPem(FakeWriter.LEAF_CERT)); + sig.initVerify(extractPublicKeyFromPem(new String( + Files.readAllBytes(FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))); sig.update(dataToSign); return sig.verify(signature); } diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java similarity index 85% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java index f130e52aac7..48c512c4e5c 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -23,9 +23,7 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.NullPointerTester.Visibility; import io.grpc.Channel; -import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; -import io.grpc.ManagedChannel; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.benchmarks.Utils; @@ -35,11 +33,9 @@ import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; -import io.grpc.s2a.channel.S2AChannelPool; -import io.grpc.s2a.channel.S2AGrpcChannelPool; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory.S2AProtocolNegotiator; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AProtocolNegotiatorFactory.S2AProtocolNegotiator; import io.grpc.stub.StreamObserver; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; @@ -50,9 +46,8 @@ import io.netty.handler.codec.http2.Http2ConnectionEncoder; import io.netty.handler.codec.http2.Http2Settings; import io.netty.util.AsciiString; +import java.io.IOException; import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.After; import org.junit.Before; @@ -111,15 +106,14 @@ public void createProtocolNegotiatorFactory_nullArgument() throws Exception { @Test public void createProtocolNegotiator_nullArgument() throws Exception { - S2AChannelPool pool = - S2AGrpcChannelPool.create( + ObjectPool pool = SharedResourcePool.forResource( S2AHandshakerServiceChannel.getChannelResource( - "localhost:8080", /* s2aChannelCredentials= */ Optional.empty()))); + "localhost:8080", InsecureChannelCredentials.create())); NullPointerTester tester = new NullPointerTester() - .setDefault(S2AChannelPool.class, pool) + .setDefault(ObjectPool.class, pool) .setDefault(Optional.class, Optional.empty()); tester.testStaticMethods(S2AProtocolNegotiator.class, Visibility.PACKAGE); @@ -174,15 +168,9 @@ public void closeProtocolNegotiator_verifyProtocolNegotiatorIsClosedOnClientSide @Test public void createChannelHandler_addHandlerToMockContext() throws Exception { - ExecutorService executor = Executors.newSingleThreadExecutor(); - ManagedChannel channel = - Grpc.newChannelBuilder(authority, InsecureChannelCredentials.create()) - .executor(executor) - .build(); - FakeS2AChannelPool fakeChannelPool = new FakeS2AChannelPool(channel); ProtocolNegotiator clientNegotiator = S2AProtocolNegotiatorFactory.S2AProtocolNegotiator.createForClient( - fakeChannelPool, LOCAL_IDENTITY); + channelPool, LOCAL_IDENTITY); ChannelHandler channelHandler = clientNegotiator.newHandler(fakeConnectionHandler); @@ -190,26 +178,6 @@ public void createChannelHandler_addHandlerToMockContext() throws Exception { verify(mockChannelHandlerContext).fireUserEventTriggered("event"); } - /** A {@link S2AChannelPool} that returns the given channel. */ - private static class FakeS2AChannelPool implements S2AChannelPool { - private final Channel channel; - - FakeS2AChannelPool(Channel channel) { - this.channel = channel; - } - - @Override - public Channel getChannel() { - return channel; - } - - @Override - public void returnToPool(Channel channel) {} - - @Override - public void close() {} - } - /** A {@code GrpcHttp2ConnectionHandler} that does nothing. */ private static class FakeConnectionHandler extends GrpcHttp2ConnectionHandler { private static final Http2ConnectionDecoder DECODER = mock(Http2ConnectionDecoder.class); @@ -246,7 +214,11 @@ public StreamObserver setUpSession(StreamObserver respo return new StreamObserver() { @Override public void onNext(SessionReq req) { - responseObserver.onNext(writer.handleResponse(req)); + try { + responseObserver.onNext(writer.handleResponse(req)); + } catch (IOException e) { + responseObserver.onError(e); + } } @Override diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java similarity index 79% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java index bb90be12b6a..bf99ef3f944 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java @@ -14,20 +14,22 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static org.junit.Assert.assertThrows; import com.google.common.truth.Expect; +import io.grpc.Channel; +import io.grpc.InsecureChannelCredentials; +import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; -import io.grpc.s2a.channel.S2AChannelPool; -import io.grpc.s2a.channel.S2AGrpcChannelPool; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; import io.grpc.stub.StreamObserver; import java.io.IOException; -import java.util.Optional; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -51,12 +53,11 @@ public void setUp() { @Test public void send_receiveOkStatus() throws Exception { - S2AChannelPool channelPool = - S2AGrpcChannelPool.create( - SharedResourcePool.forResource( - S2AHandshakerServiceChannel.getChannelResource( - S2A_ADDRESS, /* s2aChannelCredentials= */ Optional.empty()))); - S2AServiceGrpc.S2AServiceStub serviceStub = S2AServiceGrpc.newStub(channelPool.getChannel()); + ObjectPool channelPool = + SharedResourcePool.forResource( + S2AHandshakerServiceChannel.getChannelResource( + S2A_ADDRESS, InsecureChannelCredentials.create())); + S2AServiceGrpc.S2AServiceStub serviceStub = S2AServiceGrpc.newStub(channelPool.getObject()); S2AStub newStub = S2AStub.newInstance(serviceStub); IOException expected = @@ -82,9 +83,12 @@ public void send_clientTlsConfiguration_receiveOkStatus() throws Exception { GetTlsConfigurationResp.newBuilder() .setClientTlsConfiguration( GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(FakeWriter.LEAF_CERT) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) .addCiphersuites( @@ -189,26 +193,13 @@ public void send_receiveManyUnexpectedResponse_expectResponsesEmpty() throws Exc @Test public void send_receiveDelayedResponse() throws Exception { writer.sendGetTlsConfigResp(); - SessionResp resp = stub.send(SessionReq.getDefaultInstance()); - SessionResp expected = - SessionResp.newBuilder() - .setGetTlsConfigurationResp( - GetTlsConfigurationResp.newBuilder() - .setClientTlsConfiguration( - GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(FakeWriter.LEAF_CERT) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) - .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) - .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) - .build(); - assertThat(resp).ignoringRepeatedFieldOrder().isEqualTo(expected); + IOException expectedException = + assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance())); + assertThat(expectedException) + .hasMessageThat() + .contains("Received an unexpected response from a host at the S2A's address."); + + assertThat(stub.getResponses()).isEmpty(); } @Test diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2ATrustManagerTest.java similarity index 99% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2ATrustManagerTest.java index 384e1aba5cc..198001838aa 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2ATrustManagerTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.io.ByteArrayInputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/SslContextFactoryTest.java similarity index 98% rename from s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/SslContextFactoryTest.java index a2a66a7b563..fc3cfb5e441 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/SslContextFactoryTest.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.common.truth.Expect; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslSessionContext; import io.netty.handler.ssl.SslContext; import java.security.GeneralSecurityException; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java similarity index 95% rename from s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java index 80adba07f20..5bf2ce05259 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; import static com.google.common.truth.Truth.assertThat; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.util.Optional; import org.junit.Before; import org.junit.Test; diff --git a/s2a/src/test/resources/README.md b/s2a/src/test/resources/README.md index 726b921a615..2250ffb1dec 100644 --- a/s2a/src/test/resources/README.md +++ b/s2a/src/test/resources/README.md @@ -29,4 +29,41 @@ Sign CSRs for server and client ``` openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in server.csr -out server_cert.pem -days 7305 -extfile config.cnf -extensions req_ext openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305 +``` + +Generate self-signed ECDSA root cert + +``` +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out root_key_ec.pem -nocrypt +rm temp.pem +openssl req -x509 -days 7305 -new -key root_key_ec.pem -nodes -out root_cert_ec.pem -config root_ec.cnf -extensions 'v3_req' +``` + +Generate a chain of ECDSA certs + +``` +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out int_key2_ec.pem -nocrypt +rm temp.pem +openssl req -key int_key2_ec.pem -new -out temp.csr -config int_cert2.cnf +openssl x509 -req -days 7305 -in temp.csr -CA root_cert_ec.pem -CAkey root_key_ec.pem -CAcreateserial -out int_cert2_ec.pem -extfile int_cert2.cnf -extensions 'v3_req' + + +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out int_key1_ec.pem -nocrypt +rm temp.pem +openssl req -key int_key1_ec.pem -new -out temp.csr -config int_cert1.cnf +openssl x509 -req -days 7305 -in temp.csr -CA int_cert2_ec.pem -CAkey int_key2_ec.pem -CAcreateserial -out int_cert1_ec.pem -extfile int_cert1.cnf -extensions 'v3_req' + + +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out leaf_key_ec.pem -nocrypt +rm temp.pem +openssl req -key leaf_key_ec.pem -new -out temp.csr -config leaf.cnf +openssl x509 -req -days 7305 -in temp.csr -CA int_cert1_ec.pem -CAkey int_key1_ec.pem -CAcreateserial -out leaf_cert_ec.pem -extfile leaf.cnf -extensions 'v3_req' +``` + +``` +cat leaf_cert_ec.pem int_cert1_ec.pem int_cert2_ec.pem > cert_chain_ec.pem ``` \ No newline at end of file diff --git a/s2a/src/test/resources/cert_chain_ec.pem b/s2a/src/test/resources/cert_chain_ec.pem new file mode 100644 index 00000000000..a249904286c --- /dev/null +++ b/s2a/src/test/resources/cert_chain_ec.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIB6jCCAZCgAwIBAgIUA98F2JkYZAyz9BdIkBK3P8Df7OUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50 +MUNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +bGVhZk8xDzANBgNVBAsMBmxlYWZPVTEPMA0GA1UEAwwGbGVhZkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEtpTTzt2VDTP6gO4uUIpg8sB63Ff4T4YPMoIGrrn3 +tU3f9j0Ysa5/xblM0LkwRImcrKKchYDiNm1wHkWo+qDImaOBgzCBgDAOBgNVHQ8B +Af8EBAMCB4AwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1Ud +EwEB/wQCMAAwHQYDVR0OBBYEFGzFBt/E6vDJRcH+Izy4MQ9AHycqMB8GA1UdIwQY +MBaAFBYs72Jv682/xzG3Tm8hItIFis//MAoGCCqGSM49BAMCA0gAMEUCIHUcqPTB +mQ4kXE0WoOUC8ZmzvthvfKjCNe0YogcjZgwWAiEAvapmWoQIO4qie25Ae9sYRCPq +5xAHztAquk5HLfwabow= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8TCCAZagAwIBAgIUEXwpznJIlU+ELO7Qgb4UUGpfbj8wCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50 +MkNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50MUNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEoenicrtL6ezEW2yLSXADscDJQ/fdbr+vJEU/aieV +wA2EnPbrdpvQZaz+pXtuZzBLZY50XI9y33E+/PvBFtZob6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFBYs72Jv682/xzG3Tm8hItIFis//MB8G +A1UdIwQYMBaAFPhN6eGgVc36Kc50rREZhMdBIkgGMAoGCCqGSM49BAMCA0kAMEYC +IQDiPcbihg1iDi0m9CUn96IbWOTh1X75RfVJYcR3Q5T78AIhAK/fxZauDeWPzk2r +2/ohCQOZFHtAi9VRpr/TqNi3SaYt +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8DCCAZagAwIBAgIUNOH4wQEoKHvaQ9Xgd36vh5TnhfUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFcm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9v +dENOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50MkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAE44B/G4pzAvLpIUaPp8XNRtXuw8jeLgE40NjQMuqq +3jNs6ID/fv/jiRggLMXL3Tii1CisM4BRjg56/Owky1Fyv6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFPhN6eGgVc36Kc50rREZhMdBIkgGMB8G +A1UdIwQYMBaAFNHNBlllqi9koRtf7EBHjRMwVgWsMAoGCCqGSM49BAMCA0gAMEUC +IBd4bvqVeYSSUEGF1wB0KlYxn1L0Ub/LjgIUUQFAEwahAiEAgeArX63bnlI7u3dq +v/FGilvcLP3P3AvRozpHJiIZ860= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/client.csr b/s2a/src/test/resources/client.csr deleted file mode 100644 index 664f5a4cf86..00000000000 --- a/s2a/src/test/resources/client.csr +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIChzCCAW8CAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAoSS3KtFgiXX4vAUNscFGIB/r2OOMgiZMKHz72dN0 -5kSxwdpQxpMIhwEoe0lhHNfOiuE7/r6VbGG9RGGIcQcoSonc3InPRfpnzfj9KohJ -i8pYkLL9EwElAEl9sWnvVKTza8jTApDP2Z/fntBEsWAMsLPpuRZT6tgN1sXe4vNG -4wufJSxuImyCVAx1fkZjRkYEKOtm1osnEDng4R0WXZ6S+q5lYzYPk1wxgbjdZu2U -fWxP6V63SphV0NFXTx0E401j2h258cIqTVj8lRX6dfl9gO0d43Rd+hSU7R4iXGEw -arixuH9g5H745AFf9H52twHPcNP9cEKBljBpSV5z3MvTkQIDAQABoC4wLAYJKoZI -hvcNAQkOMR8wHTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3 -DQEBCwUAA4IBAQCQHim3aIpGJs5u6JhEA07Rwm8YKyVALDEklhsHILlFhdNr2uV7 -S+3bHV79mDGjxNWvFcgK5h5ENkT60tXbhbie1gYmFT0RMCYHDsL09NGTh8G9Bbdl -UKeA9DMhRSYzE7Ks3Lo1dJvX7OAEI0qV77dGpQknufYpmHiBXuqtB9I0SpYi1c4O -9IUn/NY0yiYFPsIEsVRz/1dK97wazusLnijaMwNNhUc9bJwTyujhlr+b8ioPyADG -e+GDF97d0nQ8806DOJF4GTRKwaXD+R5zN5t4ULhZ7ERqLNeE9EnWRe4CvSGvBoNA -hIVeYaLd761Z9ZKvOnsgCr8qvMDilDFY6OfB ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/s2a/src/test/resources/client_cert.pem b/s2a/src/test/resources/client_cert.pem index b72f6991c91..837f8bb5019 100644 --- a/s2a/src/test/resources/client_cert.pem +++ b/s2a/src/test/resources/client_cert.pem @@ -1,18 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIC9DCCAdwCFB+cDXee2sIHjdlBhdNpTo+G2XAjMA0GCSqGSIb3DQEBCwUAMFkx -CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl -cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzEw -MTcyMzA5MDNaFw00MzEwMTcyMzA5MDNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKEktyrRYIl1+LwFDbHBRiAf -69jjjIImTCh8+9nTdOZEscHaUMaTCIcBKHtJYRzXzorhO/6+lWxhvURhiHEHKEqJ -3NyJz0X6Z834/SqISYvKWJCy/RMBJQBJfbFp71Sk82vI0wKQz9mf357QRLFgDLCz -6bkWU+rYDdbF3uLzRuMLnyUsbiJsglQMdX5GY0ZGBCjrZtaLJxA54OEdFl2ekvqu -ZWM2D5NcMYG43WbtlH1sT+let0qYVdDRV08dBONNY9odufHCKk1Y/JUV+nX5fYDt -HeN0XfoUlO0eIlxhMGq4sbh/YOR++OQBX/R+drcBz3DT/XBCgZYwaUlec9zL05EC -AwEAATANBgkqhkiG9w0BAQsFAAOCAQEARorc1t2OJnwm1lxhf2KpTpNvNOI9FJak -iSHz/MxhMdu4BG/dQHkKkWoVC6W2Kaimx4OImBwRlGEmGf4P0bXOLSTOumk2k1np -ZUbw7Z2cJzvBmT2BLoHRXcBvbFIBW5DJUSHR37eXEKP57BeD+Og4/3XhNzehSpTX -DRd2Ix/D39JjYA462nqPHQP8HDMf6+0BFmvf9ZRYmFucccYQRCUCKDqb8+wGf9W6 -tKNRE6qPG2jpAQ9qkgO7XuucbLvpywt5xj+yDRbOIq43l40mHaz4lRp697oaxjP8 -HSVcMydW3cluoW3AVInNIaqbM1dr6931MllK62DKipFtmCycq/56XA== +MIIDPTCCAiWgAwIBAgIUaarddwSWeE4jDC9kwxEr446ehqUwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTI0MTAwMTIxNTk1NFoXDTQ0MTAwMTIxNTk1NFowFDESMBAGA1UEAwwJbG9jYWxo +b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlNsldt7yAU4KRuS +2D2/FjNIE1US5olBm4HteTr++41WaELZJqNLRPPp052jEQU3aKSYNGZvUUO6buu7 +eFpz2SBNUVMyvmzzocjVAyyf4NQvDazYHWOb+/YCeUppTRWriz4V5sn47qJTQ8cd +CGrTFeLHxUjx4nh/OiqVXP/KnF3EqPEuqph0ky7+GirnJgPRe+C5ERuGkJye8dmP +yWGA2lSS6MeDe7JZTAMi08bAn7BuNpeBkOzz1msGGI9PnUanUs7GOPWTDdcQAVY8 +KMvHCuGaNMGpb4rOR2mm8LlbAbpTPz8Pkw4QtMCLkgsrz2CzXpVwnLsU7nDXJAIO +B155lQIDAQABo0IwQDAdBgNVHQ4EFgQUSZEyIHLzkIw7AwkBaUjYfIrGVR4wHwYD +VR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4umbiwwDQYJKoZIhvcNAQELBQADggEB +AAz0bZ4ayrZLhA45xn0yvdpdqiCtiWikCRtxgE7VXHg/ziZJVMpBpAhbIGO5tIyd +lttnRXHwz5DUwKiba4/bCEFe229BshQEql5qaqcbGbFfSly11WeqqnwR1N7c8Gpv +pD9sVrx22seN0rTUk87MY/S7mzCxHqAx35zm/LTW3pWcgCTMKFHy4Gt4mpTnXkNA +WkhP2OhW5RLiu6Whi0BEdb2TGG1+ctamgijKXb+gJeef5ehlHXG8eU862KF5UlEA +NeQKBm/PpQxOMe0NdpatjN8QRoczku0Itiodng+OZ1o+2iSNG988uFRb3CUSnjtE +R/HL6ULAFzo59EpIYxruU/w= -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/client_key.pem b/s2a/src/test/resources/client_key.pem index dd3e2ff78f1..38b93eb65c4 100644 --- a/s2a/src/test/resources/client_key.pem +++ b/s2a/src/test/resources/client_key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChJLcq0WCJdfi8 -BQ2xwUYgH+vY44yCJkwofPvZ03TmRLHB2lDGkwiHASh7SWEc186K4Tv+vpVsYb1E -YYhxByhKidzcic9F+mfN+P0qiEmLyliQsv0TASUASX2xae9UpPNryNMCkM/Zn9+e -0ESxYAyws+m5FlPq2A3Wxd7i80bjC58lLG4ibIJUDHV+RmNGRgQo62bWiycQOeDh -HRZdnpL6rmVjNg+TXDGBuN1m7ZR9bE/pXrdKmFXQ0VdPHQTjTWPaHbnxwipNWPyV -Ffp1+X2A7R3jdF36FJTtHiJcYTBquLG4f2DkfvjkAV/0fna3Ac9w0/1wQoGWMGlJ -XnPcy9ORAgMBAAECggEALAUqoGDIHWUDyOEch5WDwZzWwc4PgTJTFbBm4G96fLkB -UjKAZG6gIrk3RM6b39Q4UQoMaJ/Jk+zzVi3Kpw3MfOhCVGC1JamtF8BP8IGAjdZ9 -8TFkHv/uCrEIzCFjRt00vhoDQq0qiom4/dppGYdikBbl3zDxRbM1vJkbNSY+FCGW -dA0uJ5XdMLR6lPeB5odqjUggnfUgPCOLdV/F+HkSM9NP1bzmHLiKznzwFsfat139 -7LdzJwNN5IX4Io6cxsxNlrX/NNvPkKdGv07Z6FYxWROyKCunjh48xFcQg0ltoRuq -R9P8/LwS8GYrcc1uC/uBc0e6VgM9D9fsvh+8SQtf3QKBgQDXX+z2GnsFoEs7xv9U -qN0HEX4jOkihZvFu43layUmeCeE8wlEctJ0TsM5Bd7FMoUG6e5/btwhsAIYW89Xn -l/R8OzxR6Kh952Dce4DAULuIeopiw7ASJwTZtO9lWhxw0hjM1hxXTG+xxOqQvsRX -c+d+vtvdIqyJ4ELfzg9kUtkdpwKBgQC/ig3cmej7dQdRAMn0YAwgwhuLkCqVFh4y -WIlqyPPejKf8RXubqgtaSYx/T7apP87SMMSfSLaUdrYAGjST6k+tG5cmwutPIbw/ -osL7U3hcIhjX3hfHgI69Ojcpplbd5yqTxZHpxIs6iAQCEqNuasLXIDMouqNhGF1D -YssD6qxcBwKBgQCdZqWvVrsB6ZwSG+UO4jpmqAofhMD/9FQOToCqMOF0dpP966WL -7RO/CEA06FzTPCblOuQhlyq4g8l7jMiPcSZkhIYY9oftO+Q2Pqxh4J6tp6DrfUh4 -e7u3v9wVnj2a1nD5gqFDy8D1kow7LLAhmbtdje7xNh4SxasaFWZ6U3IJkQKBgGS1 -F5i3q9IatCAZBBZjMb0/kfANevYsTPA3sPjec6q91c1EUzuDarisFx0RMn9Gt124 -mokNWEIzMHpZTO/AsOfZq92LeuF+YVYsI8y1FIGMw/csJOCWbXZ812gkt2OxGafc -p118I6BAx6q3VgrGQ2+M1JlDmIeCofa+SPPkPX+dAoGBAJrOgEJ+oyEaX/YR1g+f -33pWoPQbRCG7T4+Y0oetCCWIcMg1/IUvGUCGmRDxj5dMqB+a0vJtviQN9rjpSuNS -0EVw79AJkIjHhi6KDOfAuyBvzGhxpqxGufnQ2GU0QL65NxQfd290xkxikN0ZGtuB -SDgZoJxMOGYwf8EX5i9h27Db +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGU2yV23vIBTgp +G5LYPb8WM0gTVRLmiUGbge15Ov77jVZoQtkmo0tE8+nTnaMRBTdopJg0Zm9RQ7pu +67t4WnPZIE1RUzK+bPOhyNUDLJ/g1C8NrNgdY5v79gJ5SmlNFauLPhXmyfjuolND +xx0IatMV4sfFSPHieH86KpVc/8qcXcSo8S6qmHSTLv4aKucmA9F74LkRG4aQnJ7x +2Y/JYYDaVJLox4N7sllMAyLTxsCfsG42l4GQ7PPWawYYj0+dRqdSzsY49ZMN1xAB +Vjwoy8cK4Zo0walvis5HaabwuVsBulM/Pw+TDhC0wIuSCyvPYLNelXCcuxTucNck +Ag4HXnmVAgMBAAECggEAKuW9jXaBgiS63o1jyFkmvWcPNntG0M2sfrXuRzQfFgse +vwOCk8xrSflWQNsOe+58ayp6746ekl3LdBWSIbiy6SqG/sm3pp/LXNmjVYHv/QH4 +QYV643R5t1ihdVnGiBFhXwdpVleme/tpdjYZzgnJKak5W69o/nrgzhSK5ShAy2xM +j0XXbgdqG+4JxPb5BZmjHHfXAXUfgSORMdfArkbgFBRc9wL/6JVTXjeAMy5WX9qe +5UQsSOYkwc9P2snifC/jdIhjHQOkkx59O0FgukJEFZPoagVG1duWQbnNDr7QVHCJ +jV6dg9tIT4SXD3uPSPbgNGlRUseIakCzrhHARJuA2wKBgQD/h8zoh0KaqKyViCYw +XKOFpm1pAFnp2GiDOblxNubNFAXEWnC+FlkvO/z1s0zVuYELUqfxcYMSXJFEVelK +rfjZtoC5oxqWGqLo9iCj7pa8t+ipulYcLt2SWc7eZPD4T4lzeEf1Qz77aKcz34sa +dv9lzQkDvhR/Mv1VeEGFHiq2VwKBgQDGsLcTGH5Yxs//LRSY8TigBkQEDrH5NvXu +2jtAzZhy1Yhsoa5eiZkhnnzM6+n05ovfZLcy6s7dnwP1Y+C79vs+DKMBsodtDG5z +YpsB0VrXYa6P6pCqkcz0Bz9xdo5sOhAK3AKnX6jd29XBDdeYsw/lxHLG24wProTD +cCYFqtaj8wKBgQCaqKT68DL9zK14a8lBaDCIyexaqx3AjXzkP+Hfhi03XrEG4P5v +7rLYBeTbCUSt7vMN2V9QoTWFvYUm6SCkVJvTmcRblz6WL1T+z0l+LwAJBP7LC77m +m+77j2PH8yxt/iXhP6G97o+GNxdMLDbTM8bs5KZaH4fkXQY73uc5HMMZTQKBgEZS +7blYhf+t/ph2wD+RwVUCYrh86wkmJs2veCFro3WhlnO8lhbn5Mc9bTaqmVgQ8ZjT +8POYoDdYvPHxs+1TcYF4v4kuQziZmc5FLE/sZZauADb38tQsXrpQhmgGakpsEpmF +XXsYJJDB6lo2KATn+8x7R5SSyHQUdPEnlI2U9ft5AoGBAJw0NJiM1EzRS8xq0DmO +AvQaPjo01o2hH6wghws8gDQwrj0eHraHgVi7zo0VkaHJbO7ahKPudset3N7owJhA +CUAPPRtv5wn0amAyNz77f1dz4Gys3AkcchflqhbEaQpzKYx4kX0adclur4WJ/DVm +P7DI977SHCVB4FVMbXMEkBjN -----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/config.cnf b/s2a/src/test/resources/config.cnf index 38d9a9ccdb0..5f9a7710e92 100644 --- a/s2a/src/test/resources/config.cnf +++ b/s2a/src/test/resources/config.cnf @@ -14,4 +14,4 @@ emailAddress = Email Address subjectAltName = @alt_names [alt_names] -IP.1 = :: \ No newline at end of file +IP.1 = ::1 \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert1_.cnf b/s2a/src/test/resources/int_cert1_.cnf new file mode 100644 index 00000000000..ba5a0f66a5e --- /dev/null +++ b/s2a/src/test/resources/int_cert1_.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = int1O +OU = int1OU +CN = int1CN + +[v3_req] +keyUsage = critical, keyCertSign, cRLSign +extendedKeyUsage = critical, clientAuth, serverAuth +basicConstraints = critical, CA:true, pathlen: 1 \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert1_ec.pem b/s2a/src/test/resources/int_cert1_ec.pem new file mode 100644 index 00000000000..de83c2aba79 --- /dev/null +++ b/s2a/src/test/resources/int_cert1_ec.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB8TCCAZagAwIBAgIUEXwpznJIlU+ELO7Qgb4UUGpfbj8wCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50 +MkNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50MUNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEoenicrtL6ezEW2yLSXADscDJQ/fdbr+vJEU/aieV +wA2EnPbrdpvQZaz+pXtuZzBLZY50XI9y33E+/PvBFtZob6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFBYs72Jv682/xzG3Tm8hItIFis//MB8G +A1UdIwQYMBaAFPhN6eGgVc36Kc50rREZhMdBIkgGMAoGCCqGSM49BAMCA0kAMEYC +IQDiPcbihg1iDi0m9CUn96IbWOTh1X75RfVJYcR3Q5T78AIhAK/fxZauDeWPzk2r +2/ohCQOZFHtAi9VRpr/TqNi3SaYt +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert2.cnf b/s2a/src/test/resources/int_cert2.cnf new file mode 100644 index 00000000000..f48524effb2 --- /dev/null +++ b/s2a/src/test/resources/int_cert2.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = int2O +OU = int2OU +CN = int2CN + +[v3_req] +keyUsage = critical, keyCertSign, cRLSign +extendedKeyUsage = critical, clientAuth, serverAuth +basicConstraints = critical, CA:true, pathlen: 2 \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert2_ec.pem b/s2a/src/test/resources/int_cert2_ec.pem new file mode 100644 index 00000000000..4f502fda808 --- /dev/null +++ b/s2a/src/test/resources/int_cert2_ec.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB8DCCAZagAwIBAgIUNOH4wQEoKHvaQ9Xgd36vh5TnhfUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFcm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9v +dENOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50MkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAE44B/G4pzAvLpIUaPp8XNRtXuw8jeLgE40NjQMuqq +3jNs6ID/fv/jiRggLMXL3Tii1CisM4BRjg56/Owky1Fyv6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFPhN6eGgVc36Kc50rREZhMdBIkgGMB8G +A1UdIwQYMBaAFNHNBlllqi9koRtf7EBHjRMwVgWsMAoGCCqGSM49BAMCA0gAMEUC +IBd4bvqVeYSSUEGF1wB0KlYxn1L0Ub/LjgIUUQFAEwahAiEAgeArX63bnlI7u3dq +v/FGilvcLP3P3AvRozpHJiIZ860= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_key1_ec.pem b/s2a/src/test/resources/int_key1_ec.pem new file mode 100644 index 00000000000..909c119b60c --- /dev/null +++ b/s2a/src/test/resources/int_key1_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgnYGMzs4siZ7Fy3mI +rmsqBdP6We4Zt+ndtOYEGaZDj06hRANCAASh6eJyu0vp7MRbbItJcAOxwMlD991u +v68kRT9qJ5XADYSc9ut2m9BlrP6le25nMEtljnRcj3LfcT78+8EW1mhv +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_key2_ec.pem b/s2a/src/test/resources/int_key2_ec.pem new file mode 100644 index 00000000000..520300d2560 --- /dev/null +++ b/s2a/src/test/resources/int_key2_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgzLSoAcENXIiQfBS7 +meBDCohT1rofhWSfD0m55qi8V3WhRANCAATjgH8binMC8ukhRo+nxc1G1e7DyN4u +ATjQ2NAy6qreM2zogP9+/+OJGCAsxcvdOKLUKKwzgFGODnr87CTLUXK/ +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/leaf.cnf b/s2a/src/test/resources/leaf.cnf new file mode 100644 index 00000000000..c21cee5568f --- /dev/null +++ b/s2a/src/test/resources/leaf.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = leafO +OU = leafOU +CN = leafCN + +[v3_req] +keyUsage = critical, digitalSignature +extendedKeyUsage = critical, clientAuth, serverAuth +basicConstraints = critical, CA:false \ No newline at end of file diff --git a/s2a/src/test/resources/leaf_cert_ec.pem b/s2a/src/test/resources/leaf_cert_ec.pem new file mode 100644 index 00000000000..ca48b821f60 --- /dev/null +++ b/s2a/src/test/resources/leaf_cert_ec.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB6jCCAZCgAwIBAgIUA98F2JkYZAyz9BdIkBK3P8Df7OUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50 +MUNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +bGVhZk8xDzANBgNVBAsMBmxlYWZPVTEPMA0GA1UEAwwGbGVhZkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEtpTTzt2VDTP6gO4uUIpg8sB63Ff4T4YPMoIGrrn3 +tU3f9j0Ysa5/xblM0LkwRImcrKKchYDiNm1wHkWo+qDImaOBgzCBgDAOBgNVHQ8B +Af8EBAMCB4AwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1Ud +EwEB/wQCMAAwHQYDVR0OBBYEFGzFBt/E6vDJRcH+Izy4MQ9AHycqMB8GA1UdIwQY +MBaAFBYs72Jv682/xzG3Tm8hItIFis//MAoGCCqGSM49BAMCA0gAMEUCIHUcqPTB +mQ4kXE0WoOUC8ZmzvthvfKjCNe0YogcjZgwWAiEAvapmWoQIO4qie25Ae9sYRCPq +5xAHztAquk5HLfwabow= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/leaf_key_ec.pem b/s2a/src/test/resources/leaf_key_ec.pem new file mode 100644 index 00000000000..b92b90ba1da --- /dev/null +++ b/s2a/src/test/resources/leaf_key_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkvnGZBh3uIYfZiau +/0qN0YcQXlwwVVUh8EybjvKUlX2hRANCAAS2lNPO3ZUNM/qA7i5QimDywHrcV/hP +hg8yggauufe1Td/2PRixrn/FuUzQuTBEiZysopyFgOI2bXAeRaj6oMiZ +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_cert.pem b/s2a/src/test/resources/root_cert.pem index 737e601691c..ccd0a46bc23 100644 --- a/s2a/src/test/resources/root_cert.pem +++ b/s2a/src/test/resources/root_cert.pem @@ -1,22 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIDkzCCAnugAwIBAgIUb7RsINwsFgKf0Q0RuzfOgp48j6UwDQYJKoZIhvcNAQEL +MIIDkzCCAnugAwIBAgIUWemeXZdfqcqkP8/Eyj74oTJtoNQwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X -DTIzMTAxNzIzMDczOFoXDTQzMTAxNzIzMDczOFowWTELMAkGA1UEBhMCQVUxEzAR +DTI0MTAwMTIxNTkxMVoXDTQ0MTAwMTIxNTkxMVowWTELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAkIFnQLuhzYnm3rvmi/U7zMgEP2Tqgb3VC00frSXEV6olZcLgyC9g -0DAGdt9l9lP90DQTG5KCOtoW2BTqM/aaVpR0OaDFOCy90FIj6YyZLZ9w2PQxQcxS -GQHyEvWszTkNxeDyG1mPTj+Go8JLKqdvLg/9GUgPg6stxyAZwYhyUTGuEM4bv0sn -b3vmHRmIGJ/w6aLtd7nK8LkNHa3WVrbvRGHrzdMHfpzF/M/5fAk8GfRYugo39knf -VLKGyQCXNI8Y1iHGEmPqQZIFPTjBL6caIlbEV0VHlxoSOGB6JVxcllxAEvd6abqX -RJVJPQzzGfEnMNYp9SiZQ9bvDRUsUkWyYwIDAQABo1MwUTAdBgNVHQ4EFgQUAZMN -F9JAGHbA3jGOeu6bWFvSdWkwHwYDVR0jBBgwFoAUAZMNF9JAGHbA3jGOeu6bWFvS -dWkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAicBli36ISJFu -lrJqOHVqTeNP6go0I35VGnP44nEEP5cBvRD3XntBFEk5D3mSNNOGt+2ncxom8VR9 -FsLuTfHAipXePJI6MSxFuBPea8V/YPBs3npk5f1FRvJ5vEgtzFvBjsKmp1dS9hH0 -KUWtWcsAkO2Anc/LVc0xxSidL8NjzYoEFqiki0TNNwCJjmd9XwnBLHW38sEb/pgy -KTyRpOyG3Zg2UDjBHiXPBrmIvVFLB6+LrPNvfr1k4HjIgVY539ZXUvVMDKytMrDY -h63EMDn4kkPpxXlufgWGybjN5D51OylyWBZLe+L1DQyWEg0Vd7GwPzb6p7bmI7MP -pooqbgbDpQ== +MIIBCgKCAQEAt3A04hy5lljv86Nu0LLQZ2hA+fcImHjt1p1Mxgcta/5oxfVLcerE +ZH+DAQLDtWzp9Up/vI57MM419GIL8Iszk7hnZRS/HWJ+2jewZJtz4i/g15dLr6+1 +uabMdPOWos60BwcLMxKEe6lJO1mV4z9d4NH4mAuMIHyM+ty0Klp9MfeDJtYEh0+z +AxJUHCixDTsnKJro7My7A3ZT7bvaMfXxS7XN6qlRgBfiCmXo/GKTFfmfBW/EZGkG +XOCxE2D79wYNhC41Q/ix0kwjEeOj2vgGFoiyblSdHdzvRXzsoQTEiZSM8lJDR2IT +ZbpgbBlknMU6efNWlS8P5damB9ZWXg3x4wIDAQABo1MwUTAdBgNVHQ4EFgQUcq3d +txAVA410YWyM0B4e+4umbiwwHwYDVR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4um +biwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApZvaI9y7vjX/ +RRdvwf2Db9KlTE9nuVQ3AsrmG9Ml0p2X6U5aTetxdYBo2PuaaYHheF03JOH8zjpL +UfFzvbi52DPbfFAaDw/6NIAenXlg492leNvUFNjGGRyJO9R5/aDfv40/fT3Em5G5 +DnR8SeGQ9tI1t6xBBT+d+/MilSiEKVu8IIF/p0SwvEyR4pKo6wFVZR0ZiIj2v/FZ +P5Qk0Xhb+slpmaR3Wtx/mPl9Wb3kpPD4CAwhWDqFkKJql9/n9FvMjdwlCQKQGB26 +ZDXY3C0UTdktK5biNWRgAUVJEWBX6Q2amrxQHIn2d9RJ8uxCME/KBAntK+VxZE78 +w0JOvQ4Dpw== -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_cert_ec.pem b/s2a/src/test/resources/root_cert_ec.pem new file mode 100644 index 00000000000..3d20dcfe83c --- /dev/null +++ b/s2a/src/test/resources/root_cert_ec.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxzCCAW2gAwIBAgIUN+H7Td9dhyvMrrzZhanevAfCN34wCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFcm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9v +dENOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +cm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9vdENOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEGnS2gVv6Bs0GtuUAOebR9E0fqaj3zi9mD97B/dgi +MLENhtVPJQzeePv6Ccap+73O0BINRNOl8tlHX0YaXDeEHKNhMF8wDgYDVR0PAQH/ +BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTRzQZZZaovZKEbX+xAR40TMFYFrDAKBggqhkjOPQQD +AgNIADBFAiEAgnIyLs7FsZNsJjFgYzlaut4h23RxrpUYVCVZt/+x1Q0CIG3U6WGz +YaEyKoCtBHH9cAy76+pP/NU2f7/QuHU9Vymd +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_ec.cnf b/s2a/src/test/resources/root_ec.cnf new file mode 100644 index 00000000000..d736865c831 --- /dev/null +++ b/s2a/src/test/resources/root_ec.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = rootO +OU = rootOU +CN = rootCN + +[v3_req] +keyUsage = critical, keyCertSign, cRLSign +extendedKeyUsage = serverAuth, clientAuth +basicConstraints = critical, CA:true \ No newline at end of file diff --git a/s2a/src/test/resources/root_key.pem b/s2a/src/test/resources/root_key.pem index aae992426d7..34d0ffa61eb 100644 --- a/s2a/src/test/resources/root_key.pem +++ b/s2a/src/test/resources/root_key.pem @@ -1,30 +1,30 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQInmQVkXP3TFcCAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECGeCAVH1pefxBIIEyD3Nj1Dy19oy -fogU+z8YBLXuSCx8s3zncYPF9nYlegGSSo0ace/WxfPu8AEPus1P2MxlxfcCQ1A+ -5+vMihtEpgpTg9R4RlLAWs45jz4AduGiwqW05+W5zgDn6g7p7HIL0+M5FxKRkAW0 -KEH4Jy8Vc1XQxkhOm1Q4NLI8PT94rcBDE9Od03sdrW/hQgaOFz5AWOlT5jF1uUOz -glF1RQQxfJygTB6qlPTC3BAaiAnWij3NOg5L5vvUhjLa7iOZOhRQBRkf4YtHsM+2 -rFy8Z7MeHOvrqFf8LXosNy3JreQW036rLGR0Xh5myATkNrEwA8df37AgLUmwqyfz -hjZefPW77LgMAXlaN8s345AGikOX8yQKEFzPV/Nag32p6t4oiRRcUUfdB4wzKi6T -mzZ6lKcGR3qqL4V6lJSV3I2fmgkYZnUwymolyu+1+CVYDLuE53TBi5dRXwgOghi7 -npw7PqqQCian8yxHF9c1rYukD0ov0/y8ratjOu9XoJG2/wWQJNvDkAyc3mSJf+3y -6Wtu1qhLszU8pZOGW0fK6bGyHSp+wkoah/vRzB0+yFjvuMIG6py2ZDQeqhqS3ZV2 -nZHHjj0tZ45Wbdf4k17ujEK34pFXluPH//zADnd6ym2W0t6x+jtqR5tYu3poORQg -jFgpudkn2RUSq8N/gIiHDwblYBxU2dmyzEVudv1zNgVSHyetGLxsFoNB7Prn89rJ -u24a/xtuCyC2pshWo3KiL74hkkCsC8rLbEAAbADheb35b+Ca3JnMwgyUHbHL6Hqf -EiVIgm14lB/1uz651X58Boo6tDFkgrxEtGDUIZm8yk2n0tGflp7BtYbMCw+7gqhb -XN4hlhFDcCJm8peXcyCtGajOnBuNO9JJDNYor6QjptaIpQBFb7/0rc7kyO12BIUv -F9mrCHF18Hd/9AtUO93+tyDAnL64Jqq9tUv8dOVtIfbcHXZSYHf24l0XAiKByb8y -9NQLUZkIuF4aUZVHV8ZBDdHNqjzqVglKQlGHdw1XBexSal5pC9HvknOmWBgl0aza -flzeTRPX7TPrMJDE5lgSy58czGpvZzhFYwOp6cwpfjNsiqdzD78Zs0xsRbNg519s -d+cLmbiU3plWCoYCuDb68eZRRzT+o41+QJG2PoMCpzPw5wMLl6HuW7HXMRFpZKJc -tPKpeTIzb8hjhA+TwVIVpTPHvvQehtTUQD2mRujdvNM6PF8tnuC3F3sB3PTjeeJg -uzfEfs3BynRTIj/gX6y87gzwsrwWIEN6U0cCbQ6J1EcgdQCiH8vbhIgfd4DkLgLN -Kkif+fI/HgBOqaiwSw3sHmWgB6PllVQOKH6qAiejTHR/UUvJTPvgKJFLunmBiF12 -N1bRge1sSXE1eLKVdi+dP1j0o6RxhaRrbX7ie3y/wYHwCJnb8h08DEprgCqoswFs -SuNKmvlibBHAsnOdhyCTOd9I5n8XzAUUp6mT+C5WDfl7qfYvh6IHFlSrhZ9aS9b6 -RY873cnphKbqU5d7Cr8Ufx4b4SgS+hEnuP8y5IToLQ3BONGQH2lu7nmd89wjW0uo -IMRXybwf/5FnKhEy8Aw+pD6AxiXC3DZVTKl3SHmjkYBDvNElsJVgygVTKgbOa1Z+ -ovIK/D7QV7Nv3uVortH8XA== +MIIFJDBWBgkqhkiG9w0BBQ0wSTAxBgkqhkiG9w0BBQwwJAQQJXNe391O3gaNbKLw +o60XrQICCAAwDAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQI4pf69+BBF8IEggTI +JuQ3p67U9k/NWMuYXaR9a6lv24YZ1qR6ieL5B6keCaCDVoQMb5V22O0vBqCVePgr +EG0yWIeeAsARMzAxE7Lnil6abSe7tij+LjEI9F7mV/1QSFt03PLVI+e7OcKNI+Nr +6vISEi8CaddekP8JDRhPMpgdWderZvogo3REpJ8GNIUddQzu1e3ZgDtOPquqcgqb +MH/HuPE3vjj4/l6ZpX+6DZKIvzjwtBQ4PMzSWLumzmYLItd3kz7UryN+9hKluSZp +D2KB24aUIQFbDxe2DMTi5c0QIiyzjwkv081ecNJOy2gYX3uiucr8/Ax3o21RNZtI +oKCmSPVEfYdrkdfkwuSOioVTbWBZBcSZo3L2bmCkSXTuheGurEw/TtQWXBgew0Bn +UQjEJgZy96PVsQeu3t+NRCacARQi4vfv7PVHlQW8fcfcC6CeNw7VIZ8aS7supqym +RJxzMY9ZnLwO9cgybXLYgosVZnvI7nOokJPfO1+KqBK01C1Sgc3tg8czKhRuztHu +qDO0GCZ7l+9/ku/WIy/5NiatNvRo5dMAOGxsSrjI9a7+EmenoIfd8/KREVX19D+R +gZRALVATHq83rF6BdsyTwya1QUr/J24EIlkOc4HbCBm5WxA2ZjNdDBZ+KhivYaS7 +l1qrbkFOhmBD9kYRbseBrxlzKUWJMGhOpw3xebut3HngLqyezLcjsXQuF3Iau5Hl +9QFcmSdLj2ZlNlQvmfNJX/r6a/K2LigruXCbvHWMqVsHd7XZdWJ/8wjm2AL97iON +mYFLP+ScfYom9qrF41jNkUKZiLk/ppvSHyWBAqbze+R9Zfpcf8ArCwuAL/JlEMzv +YkBv1DWKfzJpZHYX695MxrpS3C8m0IyXNxktBL3KTVvwZaIhSNBlNS3fdb9m8toR +Tz/LS8jseWpZ5D552/+KAa0Skhav3ZFpxmAS8BEyE/nI9Dwg9niYcZLWORWHAQPp +jraG0BkE7bn5No/k7E4rjFb+2N+36QxVacJI3neC8bQXVHP0BVUvrabOWFPnGivl +Ok91Eo8q5PUAsd15ZnKjTHzlD7zv7fF6ncBgj3P4L2Xrs6P34JOZEd4wixEUZYeC +Xe+SZrFyUr6CcNC45C6R3hDYqmrz0GK1ikkis3XcKT+C5flBYb9NRx8G9wyCuS6H +oHl0Rfbpc47wQTuajicMVO2El7syMPUAxjo3EfMzvjm7uCXLTHnXRnRt3Y5AkPGa +0kFE9Vm00PReRfQ7qbSUiOOHYa9NIsw1l2ZI+knP9XbY2HikELOpjgucrMxZF+ms +zit5YGD3NGZi5xcHZFZTs9L8kaJccXn5DtjA30eEiFzKqMtMKnwlrbSL55I1JXim +co1RLpRK2KQmtJHo1br3RH6jP7fePYzgDceDds5HKWz22pYFcVtlx4DeYH5vjdEp +i3yNQZ32jD2HYhgCK325QLP5S2UYmUOPWd4sEiwZMBPpPOlt0TqCdFKYgS2GHlSN +IYVBYelPUYsz9Kg0TFtLMZLNUmwsXJ+jqnLVtmFyoV6IIvbSCqQ9jxTbZQKxThK8 +A1G+nXBO41ZW8eQZUGx8CzbCj2JvtVThgErSRqAuYbvlUt7EI4Ac8veZC8rJIG0Q +ADkueb978o4OI6vpOdTYCmdTIoHWlpup -----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_key_ec.pem b/s2a/src/test/resources/root_key_ec.pem new file mode 100644 index 00000000000..5560a66d414 --- /dev/null +++ b/s2a/src/test/resources/root_key_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjfTyzPIlKV0zANQP +2s1C2FhbenE34QEsf83wjpuQrZWhRANCAAQadLaBW/oGzQa25QA55tH0TR+pqPfO +L2YP3sH92CIwsQ2G1U8lDN54+/oJxqn7vc7QEg1E06Xy2UdfRhpcN4Qc +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/server.csr b/s2a/src/test/resources/server.csr deleted file mode 100644 index 1657b191133..00000000000 --- a/s2a/src/test/resources/server.csr +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIChzCCAW8CAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAlPThqu8tfJ4hQKRiUw/vNPfo2L2LQU8NlrRL7rvV -71E345LGK1h/hM3MHp5VgEvaaIibb0hSNv/TYz3HVCQyNuPlcmkHZTJ9mB0icilU -rYWdM0LPIg46iThmIQVhMiNfpMKQLDLQ7o3Jktjm32OxnQdtYSV+7NFnw8/0pB4j -iaiBYfZIMeGzEJIOFG8GSNJG0pfCI71DyLRonIcb2XzfeDPHeWSF7lbIoMGAuKIE -2mXpwHmAjTMJzIShSgLqCvmbz7wR3ZeVMknXcgcqMmagGphy8SjizIWC5KRbrnRq -F22Ouxdat6scIevRXGp5nYawFYdpK9qo+82gEouVX3dtSQIDAQABoC4wLAYJKoZI -hvcNAQkOMR8wHTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3 -DQEBCwUAA4IBAQB2qU354OlNVunhZhiOFNwabovxLcgKoQz+GtJ2EzsMEza+NPvV -dttPxXzqL/U+gDghvGzSYGuh2yMfTTPO+XtZKpvMUmIWonN5jItbFwSTaWcoE8Qs -zFZokRuFJ9dy017u642mpdf6neUzjbfCjWs8+3jyFzWlkrMF3RlSTxPuksWjhXsX -dxxLNu8YWcsYRB3fODHqrlBNuDn+9kb9z8to+yq76MA0HtdDkjd/dfgghiTDJhqm -IcwhBXufwQUrOP4YiuiwM0mo7Xlhw65gnSmRcwR9ha98SV2zG5kiRYE+m+94bDbd -kGBRfhpQSzh1w09cVzmLgzkfxRShEB+bb9Ss ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/s2a/src/test/resources/server_cert.pem b/s2a/src/test/resources/server_cert.pem index 10a98cf5c21..909b83aa903 100644 --- a/s2a/src/test/resources/server_cert.pem +++ b/s2a/src/test/resources/server_cert.pem @@ -1,20 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIUMZkgD5gtoa39H9jdI/ijVkyxC/swDQYJKoZIhvcNAQEL +MIIDWjCCAkKgAwIBAgIUAeWzyzIEetYf+ZWHj9NzH1JkLYkwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X -DTIzMTAxNzIzMDg1M1oXDTQzMTAxNzIzMDg1M1owFDESMBAGA1UEAwwJbG9jYWxo -b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlPThqu8tfJ4hQKRi -Uw/vNPfo2L2LQU8NlrRL7rvV71E345LGK1h/hM3MHp5VgEvaaIibb0hSNv/TYz3H -VCQyNuPlcmkHZTJ9mB0icilUrYWdM0LPIg46iThmIQVhMiNfpMKQLDLQ7o3Jktjm -32OxnQdtYSV+7NFnw8/0pB4jiaiBYfZIMeGzEJIOFG8GSNJG0pfCI71DyLRonIcb -2XzfeDPHeWSF7lbIoMGAuKIE2mXpwHmAjTMJzIShSgLqCvmbz7wR3ZeVMknXcgcq -MmagGphy8SjizIWC5KRbrnRqF22Ouxdat6scIevRXGp5nYawFYdpK9qo+82gEouV -X3dtSQIDAQABo18wXTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMB0GA1Ud -DgQWBBTKJU+NK7Q6ZPccSigRCMBCBgjkaDAfBgNVHSMEGDAWgBQBkw0X0kAYdsDe -MY567ptYW9J1aTANBgkqhkiG9w0BAQsFAAOCAQEAXuCs6MGVoND8TaJ6qaDmqtpy -wKEW2hsGclI9yv5cMS0XCVTkmKYnIoijtqv6Pdh8PfhIx5oJqJC8Ml16w4Iou4+6 -kKF0DdzdQyiM0OlNCgLYPiR4rh0ZCAFFCvOsDum1g+b9JTFZGooK4TMd9thwms4D -SqpP5v1NWf/ZLH5TYnp2CkPzBxDlnMJZphuWtPHL+78TbgQuQaKu2nMLBGBJqtFi -HDOGxckgZuwBsy0c+aC/ZwaV7FdMP42kxUZduCEx8+BDSGwPoEpz6pwVIkjiyYAm -3O8FUeEPzYzwpkANIbbEIDWV6FVH9IahKRRkE+bL3BqoQkv8SMciEA5zWsPrbA== +DTI0MTAwMTIxNTk0NloXDTQ0MTAwMTIxNTk0NlowFDESMBAGA1UEAwwJbG9jYWxo +b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1qnW7Pb06MgRNLzt +icv/ydl8W/lpPRjrJJb04/TtXbJ1hjnp7i796TfNGrJgHqEZnaR8q83lO0L38B2X +sJ04b3R+y+6HhH8+MbHejM7ybrTZRNQXip/Kxu4QLHBTQEsplycWLf42/R3cIk/X +vgxq5NsCsbk4xI4xwlcqC8FM1AHU0VrKxzHWVhZEM+/KovBAr/hRYln9CukeKjOf +UiVq58uuDAlJRC3yH2Rd/sqCDELvqRv17J6eYx2nJ3mSN5aBa0FwVjg6vr5Obddj +AWWIkgrlAr+a+OraxOrWElFfChBSvr/qHdJFWHeCdq/SAhow5uRhC69ScJf+7lrX +hsj1sQIDAQABo18wXTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAABMB0GA1Ud +DgQWBBRdDRg6GuDj8Sujmz4/rqfP0jZHbTAfBgNVHSMEGDAWgBRyrd23EBUDjXRh +bIzQHh77i6ZuLDANBgkqhkiG9w0BAQsFAAOCAQEAAEUS27+6p88CWYemMOY0iu0e +mp4YqG0XQSilbSnxrqnJb3N8pR3Yh6JJKnblQ6xdexfzrXlBA/v7nx+f8e9HS2QZ +KLtEIaEvNKL51JdOS6ebEzLVvhk98r2kpKM3wpT++/18HPlPK5W3rMQNsLOyAdvP +UX6TakhIfflRjz1DYXQ1ERvJOFw2HEmw6K6r2VwBhZKfwwzxmAHpVwniWXGbgyRF +79hG6rO1tv1K5LHAPIRs0h2Lh/VPxm2XiaNkdGyarUy5/NM+GoHErgxOBmYltn5Q +vAlZrgF2/mSXcUb7EHoXvoC9L4M7U/dRQD4Q1fQRJ/KjrhbDAC3gfZ4zorKoaQ== -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/server_key.pem b/s2a/src/test/resources/server_key.pem index 44f087dee94..edc37cb3855 100644 --- a/s2a/src/test/resources/server_key.pem +++ b/s2a/src/test/resources/server_key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCU9OGq7y18niFA -pGJTD+809+jYvYtBTw2WtEvuu9XvUTfjksYrWH+EzcwenlWAS9poiJtvSFI2/9Nj -PcdUJDI24+VyaQdlMn2YHSJyKVSthZ0zQs8iDjqJOGYhBWEyI1+kwpAsMtDujcmS -2ObfY7GdB21hJX7s0WfDz/SkHiOJqIFh9kgx4bMQkg4UbwZI0kbSl8IjvUPItGic -hxvZfN94M8d5ZIXuVsigwYC4ogTaZenAeYCNMwnMhKFKAuoK+ZvPvBHdl5UySddy -ByoyZqAamHLxKOLMhYLkpFuudGoXbY67F1q3qxwh69FcanmdhrAVh2kr2qj7zaAS -i5Vfd21JAgMBAAECggEACTBuN4hXywdKT92UP0GNZTwh/jT7QUUqNnDa+lhWI1Rk -WUK1vPjRrRSxEfZ8mdSUHbzHsf7JK6FungGyqUsuWdqHTh6SmTibLOYnONm54paK -kx38/0HXdJ2pF0Jos5ohDV3/XOqpnv3aQJfm7kMNMv3BTqvsf5mPiDHtCq7dTGGj -rGiLc0zirKZq79C6YSB1UMB01BsDl2ScflK8b3osT18uYx/BOdjLT4yZWQsU/nbB -OeF+ziWTTUAVjodGeTf+NYG7cFN/9N9PdSnAwuw8Nche3xZKbHTh2I578Zd4bsDX -H+hoMN862nzOXEvD6KyLB8xDdnEZ+p+njeDROJVmgQKBgQDQhzQEl/co1LYc5IDO -mynhCOtKJeRWBLhYEPIuaSY3qF+lrOWzqyOUNppWDx+HeKOq70X1Q+ETeSXtbaL1 -qHBkNcApQ2lStcpkR9whcVbr9NIWC8y8UQxyerEK3x3l0bZ99dfJ/z6lbxdS7prc -Hhxy6pUj8Q8AgpTZA8HfQUF1EQKBgQC23ek24kTVvWeWX2C/82H1Yfia6ITL7WHz -3aEJaZaO5JD3KmOSZgY88Ob3pkDTRYjFZND5zSB7PnM68gpo/OEDla6ZYtfwBWCX -q4QhFtv2obehobmDk+URVfvlOcBikoEP1i8oy7WdZ5CgC4gNKkkD15l68W+g5IIG -2ZOA97yUuQKBgDAzoI2TRxmUGciR9UhMy6Bt/F12ZtKPYsFQoXqi6aeh7wIP9kTS -wXWoLYLJGiOpekOv7X7lQujKbz7zweCBIAG5/wJKx9TLms4VYkgEt+/w9oMMFTZO -kc8Al14I9xNBp6p0In5Z1vRMupp79yX8e90AZpsZRLt8c8W6PZ1Kq0PRAoGBAKmD -7LzD46t/eJccs0M9CoG94Ac5pGCmHTdDLBTdnIO5vehhkwwTJ5U2e+T2aQFwY+kY -G+B1FrconQj3dk78nFoGV2Q5DJOjaHcwt7s0xZNLNj7O/HnMj3wSiP9lGcJGrP1R -P0ZCEIlph9fU2LnbiPPW2J/vT9uF+EMBTosvG9GBAoGAEVaDLLXOHj+oh1i6YY7s -0qokN2CdeKY4gG7iKjuDFb0r/l6R9uFvpUwJMhLEkF5SPQMyrzKFdnTpw3n/jnRa -AWG6GoV+D7LES+lHP5TXKKijbnHJdFjW8PtfDXHCJ6uGG91vH0TMMp1LqhcvGfTv -lcNGXkk6gUNSecxBC1uJfKE= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWqdbs9vToyBE0 +vO2Jy//J2Xxb+Wk9GOsklvTj9O1dsnWGOenuLv3pN80asmAeoRmdpHyrzeU7Qvfw +HZewnThvdH7L7oeEfz4xsd6MzvJutNlE1BeKn8rG7hAscFNASymXJxYt/jb9Hdwi +T9e+DGrk2wKxuTjEjjHCVyoLwUzUAdTRWsrHMdZWFkQz78qi8ECv+FFiWf0K6R4q +M59SJWrny64MCUlELfIfZF3+yoIMQu+pG/Xsnp5jHacneZI3loFrQXBWODq+vk5t +12MBZYiSCuUCv5r46trE6tYSUV8KEFK+v+od0kVYd4J2r9ICGjDm5GELr1Jwl/7u +WteGyPWxAgMBAAECggEAFEAgcOlZME6TZPS/ueSfRET6mNieB2/+2sxM3OZhsBmi +QZ/cBCa1uFcVx8N1Et6iwn7ebfy199G4/xNjmHs0dDs6rPVbHnI8hUag1oq9TxlL +d9VERUUOxZZ2uyJ7kBCnI0XCL2OQf29eMXRzx093lBBfIDH3e39ojUtYwZQiMcuw +EPry0k4fVhymhKg9Wnmt5lMg4Mdc1TpPfmNFuTR0PZ1nAaVQglvH66qNKGVoWEhZ +paNLaKC4H2Jfa1AfAWl6Efy5JDMOfHF0ww0cDUrTzAeQ7jEh0UGyL1lX8W6kKRDa +0quUqxOJz9aQ8cyd27s2OQMlRtbXi/jhhVp7WLIrWQKBgQD9gKG5CgBO/L8nIj5o +EhHFhtfjEhdeXTAlenmxoBxUN7Pwkc2OvhNef7+T0+euwl50ieopWLoRxLZ2yY8l +E2b2+7EM6/8/wgt1bCVh5NCWrE63tLCx+wdht1oqciDXvuv5bJTf73sipgDTYYSV +gE+DHXq96mxVJXo1TLtQQpXMVQKBgQDYx0AbO0KP2TTNY5ChqVwthaETHjWs6z9p +U5WRgNYeXbUKg3l7JJk6zq72ZIBeqEr3d9mJqrk6HFKTh4c+LyjKyLjmY5wkmfHh +s6s1lCEgEoXKT3Fa+DxlsXltyxrJLzuf1h276jeL5bB6BmJNKLODcEoCx/ubrwOj +prdUSWqf7QKBgQCO/sg7AJE7/QY2pPJe8hJkQbP1unbEG/zUp0mOEKrqNqGhyh0R +r9ZtL9J5KMc/pRRy2Hjl6c7LxxLF3tyIJXGnUEKG73iEFokwK1jK569hzsB4j8w8 +GUYIsMyDtO0hxeiGQeGYkBX9bXZ5xkBrtH0lkLNz/ZAuV32gIzBmDalCIQKBgDGT +f+m6Z8KWHilKt+0A2n/eq7O/mO7u7hWcc/xOxqkzLRA2eTXcbN6yHfljiqgbPOnT +kwCU9r9/crMir59dEasutHqcFT2Zp2PCv0kFk33OPqLCAF6ZntZy/B5L8NhJ4Qzw +3uP28LUh1nZRt3GF+Wf56jMwoS49nEt0+UBhee0RAoGAS9YsJkbjBg2p3Gxvo5c0 +IjfZdcyS2ndTjXv+hFvkjMw0ULFT3dqpk+0asaCh5nrDUbVQyan+D8LgwSwNZy89 +e99bl//oliv/Om7lVFCKtBOhe+fIWHlrR0e2bemsQi/pgTURjYFuvjhR50dcKx96 +jLHvG4mTfStHaJ1gKGWvgWA= -----END PRIVATE KEY----- \ No newline at end of file diff --git a/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java b/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java index 4664dbcc436..c77f7f8945a 100644 --- a/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java +++ b/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java @@ -21,6 +21,7 @@ import io.grpc.NameResolver; import io.grpc.NameResolverProvider; import io.grpc.Status; +import io.grpc.StatusOr; import java.net.SocketAddress; import java.net.URI; import java.util.Collection; @@ -81,9 +82,10 @@ public void start(Listener2 listener) { if (shutdown) { listener.onError(Status.FAILED_PRECONDITION.withDescription("Resolver is shutdown")); } else { - listener.onResult( + listener.onResult2( ResolutionResult.newBuilder() - .setAddresses(ImmutableList.of(new EquivalentAddressGroup(address))) + .setAddressesOrError( + StatusOr.fromValue(ImmutableList.of(new EquivalentAddressGroup(address)))) .build()); } } diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 626c2e1104e..3e56f41d038 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -24,7 +24,8 @@ import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Objects; +import com.google.common.collect.Maps; import io.grpc.Attributes; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; @@ -37,12 +38,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -55,7 +53,8 @@ public abstract class MultiChildLoadBalancer extends LoadBalancer { private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName()); - private final Map childLbStates = new LinkedHashMap<>(); + // Modify by replacing the list to release memory when no longer used. + private List childLbStates = new ArrayList<>(0); private final Helper helper; // Set to true if currently in the process of handling resolved addresses. protected boolean resolvingAddresses; @@ -79,11 +78,13 @@ protected MultiChildLoadBalancer(Helper helper) { /** * Override to utilize parsing of the policy configuration or alternative helper/lb generation. - * Override this if keys are not Endpoints or if child policies have configuration. + * Override this if keys are not Endpoints or if child policies have configuration. Null map + * values preserve the child without delivering the child an update. */ protected Map createChildAddressesMap( ResolvedAddresses resolvedAddresses) { - Map childAddresses = new HashMap<>(); + Map childAddresses = + Maps.newLinkedHashMapWithExpectedSize(resolvedAddresses.getAddresses().size()); for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) { ResolvedAddresses addresses = resolvedAddresses.toBuilder() .setAddresses(Collections.singletonList(eag)) @@ -143,7 +144,7 @@ public void handleNameResolutionError(Status error) { @Override public void shutdown() { logger.log(Level.FINE, "Shutdown"); - for (ChildLbState state : childLbStates.values()) { + for (ChildLbState state : childLbStates) { state.shutdown(); } childLbStates.clear(); @@ -168,37 +169,37 @@ protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal( return new AcceptResolvedAddrRetVal(unavailableStatus, null); } - updateChildrenWithResolvedAddresses(newChildAddresses); - - return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildAddresses.keySet())); + List removed = updateChildrenWithResolvedAddresses(newChildAddresses); + return new AcceptResolvedAddrRetVal(Status.OK, removed); } - private void updateChildrenWithResolvedAddresses( + /** Returns removed children. */ + private List updateChildrenWithResolvedAddresses( Map newChildAddresses) { + // Create a map with the old values + Map oldStatesMap = + Maps.newLinkedHashMapWithExpectedSize(childLbStates.size()); + for (ChildLbState state : childLbStates) { + oldStatesMap.put(state.getKey(), state); + } + + // Move ChildLbStates from the map to a new list (preserving the new map's order) + List newChildLbStates = new ArrayList<>(newChildAddresses.size()); for (Map.Entry entry : newChildAddresses.entrySet()) { - ChildLbState childLbState = childLbStates.get(entry.getKey()); + ChildLbState childLbState = oldStatesMap.remove(entry.getKey()); if (childLbState == null) { childLbState = createChildLbState(entry.getKey()); - childLbStates.put(entry.getKey(), childLbState); } - childLbState.setResolvedAddresses(entry.getValue()); // update child - childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB - } - } - - /** - * Identifies which children have been removed (are not part of the newChildKeys). - */ - private List getRemovedChildren(Set newChildKeys) { - List removedChildren = new ArrayList<>(); - // Do removals - for (Object key : ImmutableList.copyOf(childLbStates.keySet())) { - if (!newChildKeys.contains(key)) { - ChildLbState childLbState = childLbStates.remove(key); - removedChildren.add(childLbState); + newChildLbStates.add(childLbState); + if (entry.getValue() != null) { + childLbState.setResolvedAddresses(entry.getValue()); // update child + childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB } } - return removedChildren; + + childLbStates = newChildLbStates; + // Remaining entries in map are orphaned + return new ArrayList<>(oldStatesMap.values()); } protected final void shutdownRemoved(List removedChildren) { @@ -233,18 +234,17 @@ protected final Helper getHelper() { @VisibleForTesting public final Collection getChildLbStates() { - return childLbStates.values(); + return childLbStates; } @VisibleForTesting public final ChildLbState getChildLbState(Object key) { - if (key == null) { - return null; - } - if (key instanceof EquivalentAddressGroup) { - key = new Endpoint((EquivalentAddressGroup) key); + for (ChildLbState state : childLbStates) { + if (Objects.equal(state.getKey(), key)) { + return state; + } } - return childLbStates.get(key); + return null; } @VisibleForTesting diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 0ea2c7dd75f..2a9435aa72f 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -163,6 +163,13 @@ public void handleNameResolutionError(Status error) { } } + @Override + public void requestConnection() { + if (childSwitchLb != null) { + childSwitchLb.requestConnection(); + } + } + @Override public void shutdown() { if (dropStats != null) { @@ -236,7 +243,8 @@ public void start(SubchannelStateListener listener) { delegate().start(new SubchannelStateListener() { @Override public void onSubchannelState(ConnectivityStateInfo newState) { - if (newState.getState().equals(ConnectivityState.READY)) { + // Do nothing if LB has been shutdown + if (xdsClient != null && newState.getState().equals(ConnectivityState.READY)) { // Get locality based on the connected address attributes ClusterLocality updatedClusterLocality = createClusterLocalityFromAttributes( subchannel.getConnectedAddressAttributes()); diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index c175b847c63..2573a7293d3 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -77,57 +77,43 @@ protected ChildLbState createChildLbState(Object key) { @Override protected Map createChildAddressesMap( ResolvedAddresses resolvedAddresses) { + lastResolvedAddresses = resolvedAddresses; + ClusterManagerConfig config = (ClusterManagerConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); Map childAddresses = new HashMap<>(); - if (config != null) { - for (Map.Entry childPolicy : config.childPolicies.entrySet()) { - ResolvedAddresses addresses = resolvedAddresses.toBuilder() - .setLoadBalancingPolicyConfig(childPolicy.getValue()) - .build(); - childAddresses.put(childPolicy.getKey(), addresses); + + // Reactivate children with config; deactivate children without config + for (ChildLbState rawState : getChildLbStates()) { + ClusterManagerLbState state = (ClusterManagerLbState) rawState; + if (config.childPolicies.containsKey(state.getKey())) { + // Active child + if (state.deletionTimer != null) { + state.reactivateChild(); + } + } else { + // Inactive child + if (state.deletionTimer == null) { + state.deactivateChild(); + } + if (state.deletionTimer.isPending()) { + childAddresses.put(state.getKey(), null); // Preserve child, without config update + } } } + + for (Map.Entry childPolicy : config.childPolicies.entrySet()) { + ResolvedAddresses addresses = resolvedAddresses.toBuilder() + .setLoadBalancingPolicyConfig(childPolicy.getValue()) + .build(); + childAddresses.put(childPolicy.getKey(), addresses); + } logger.log( XdsLogLevel.INFO, - "Received cluster_manager lb config: child names={0}", childAddresses.keySet()); + "Received cluster_manager lb config: child names={0}", config.childPolicies.keySet()); return childAddresses; } - /** - * This is like the parent except that it doesn't shutdown the removed children since we want that - * to be done by the timer. - */ - @Override - public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { - if (lastResolvedAddresses != null) { - // Handle deactivated children - ClusterManagerConfig config = (ClusterManagerConfig) - resolvedAddresses.getLoadBalancingPolicyConfig(); - ClusterManagerConfig lastConfig = (ClusterManagerConfig) - lastResolvedAddresses.getLoadBalancingPolicyConfig(); - Map adjChildPolicies = new HashMap<>(config.childPolicies); - for (Entry entry : lastConfig.childPolicies.entrySet()) { - ClusterManagerLbState state = (ClusterManagerLbState) getChildLbState(entry.getKey()); - if (adjChildPolicies.containsKey(entry.getKey())) { - if (state.deletionTimer != null) { - state.reactivateChild(); - } - } else if (state != null) { - adjChildPolicies.put(entry.getKey(), entry.getValue()); - if (state.deletionTimer == null) { - state.deactivateChild(); - } - } - } - config = new ClusterManagerConfig(adjChildPolicies); - resolvedAddresses = - resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config).build(); - } - lastResolvedAddresses = resolvedAddresses; - return super.acceptResolvedAddresses(resolvedAddresses); - } - /** * Using the state of all children will calculate the current connectivity state, * update currentConnectivityState, generate a picker and then call @@ -232,14 +218,6 @@ class DeletionTask implements Runnable { @Override public void run() { - ClusterManagerConfig config = (ClusterManagerConfig) - lastResolvedAddresses.getLoadBalancingPolicyConfig(); - Map childPolicies = new HashMap<>(config.childPolicies); - Object removed = childPolicies.remove(getKey()); - assert removed != null; - config = new ClusterManagerConfig(childPolicies); - lastResolvedAddresses = - lastResolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config).build(); acceptResolvedAddresses(lastResolvedAddresses); } } diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index aaaed9554f4..7eba43ce278 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -52,6 +53,7 @@ import io.grpc.SynchronizationContext; import io.grpc.internal.FakeClock; import io.grpc.internal.ObjectPool; +import io.grpc.internal.PickFirstLoadBalancerProvider; import io.grpc.internal.PickSubchannelArgsImpl; import io.grpc.protobuf.ProtoUtils; import io.grpc.testing.TestMethodDescriptors; @@ -280,6 +282,7 @@ public void pick_addsLocalityLabel() { FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -309,6 +312,7 @@ public void recordLoadStats() { FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); Subchannel subchannel = leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -381,9 +385,7 @@ public void recordLoadStats() { assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported } - // TODO(dnvindhya): This test has been added as a fix to verify - // https://github.com/grpc/grpc-java/issues/11434. - // Once we update PickFirstLeafLoadBalancer as default LoadBalancer, update the test. + // Verifies https://github.com/grpc/grpc-java/issues/11434. @Test public void pickFirstLoadReport_onUpdateAddress() { Locality locality1 = @@ -407,6 +409,7 @@ public void pickFirstLoadReport_onUpdateAddress() { // Leaf balancer is created by Pick First. Get FakeSubchannel created to update attributes // A real subchannel would get these attributes from the connected address's EAG locality. FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -431,8 +434,17 @@ public void pickFirstLoadReport_onUpdateAddress() { fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); // Faksubchannel mimics update address and returns different locality - fakeSubchannel.setConnectedEagIndex(1); - fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + if (PickFirstLoadBalancerProvider.isEnabledNewPickFirst()) { + fakeSubchannel.updateState(ConnectivityStateInfo.forTransientFailure( + Status.UNAVAILABLE.withDescription("Try second address instead"))); + fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + } else { + fakeSubchannel.setConnectedEagIndex(1); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + } result = currentPicker.pickSubchannel(pickSubchannelArgs); assertThat(result.getStatus().isOk()).isTrue(); ClientStreamTracer streamTracer2 = result.getStreamTracerFactory().newClientStreamTracer( @@ -490,6 +502,7 @@ public void dropRpcsWithRespectToLbConfigDropCategories() { .isEqualTo(endpoint.getAddresses()); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); @@ -571,6 +584,7 @@ private void subtest_maxConcurrentRequests_appliedByLbConfig(boolean enableCircu .isEqualTo(endpoint.getAddresses()); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -665,6 +679,7 @@ private void subtest_maxConcurrentRequests_appliedWithDefaultValue( .isEqualTo(endpoint.getAddresses()); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -943,6 +958,7 @@ Subchannel createSubChannel() { new FixedResultPicker(PickResult.withSubchannel(subchannel))); } }); + subchannel.requestConnection(); return subchannel; } } @@ -989,6 +1005,8 @@ private static final class FakeSubchannel extends Subchannel { private final Attributes attrs; private SubchannelStateListener listener; private Attributes connectedAttributes; + private ConnectivityStateInfo state = ConnectivityStateInfo.forNonError(ConnectivityState.IDLE); + private boolean connectionRequested; private FakeSubchannel(List eags, Attributes attrs) { this.eags = eags; @@ -1006,6 +1024,9 @@ public void shutdown() { @Override public void requestConnection() { + if (state.getState() == ConnectivityState.IDLE) { + this.connectionRequested = true; + } } @Override @@ -1028,6 +1049,26 @@ public Attributes getConnectedAddressAttributes() { } public void updateState(ConnectivityStateInfo newState) { + switch (newState.getState()) { + case IDLE: + assertThat(state.getState()).isEqualTo(ConnectivityState.READY); + break; + case CONNECTING: + assertThat(state.getState()) + .isIn(Arrays.asList(ConnectivityState.IDLE, ConnectivityState.TRANSIENT_FAILURE)); + if (state.getState() == ConnectivityState.IDLE) { + assertWithMessage("Connection requested").that(this.connectionRequested).isTrue(); + this.connectionRequested = false; + } + break; + case READY: + case TRANSIENT_FAILURE: + assertThat(state.getState()).isEqualTo(ConnectivityState.CONNECTING); + break; + default: + break; + } + this.state = newState; listener.onSubchannelState(newState); } diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index d3c6bc8f9a0..0ecd77b12cb 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -52,6 +52,7 @@ import io.grpc.NameResolverRegistry; import io.grpc.Status; import io.grpc.Status.Code; +import io.grpc.StatusOr; import io.grpc.SynchronizationContext; import io.grpc.internal.BackoffPolicy; import io.grpc.internal.FakeClock; @@ -1306,7 +1307,8 @@ public void shutdown() { } private void deliverEndpointAddresses(List addresses) { - listener.onResult(ResolutionResult.newBuilder().setAddresses(addresses).build()); + listener.onResult(ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromValue(addresses)).build()); } private void deliverError(Status error) { diff --git a/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java index 659bacd3626..64e18465597 100644 --- a/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java @@ -252,7 +252,7 @@ private List getSubchannels(LeastRequestLoadBalancer lb) { private LeastRequestLbState getChildLbState(PickResult pickResult) { EquivalentAddressGroup eag = pickResult.getSubchannel().getAddresses(); - return (LeastRequestLbState) loadBalancer.getChildLbState(eag); + return (LeastRequestLbState) loadBalancer.getChildLbStateEag(eag); } @Test diff --git a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java index 047ba71bbe0..dd7de3691a7 100644 --- a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java @@ -150,7 +150,8 @@ public void subchannelLazyConnectUntilPicked() { assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getSubchannel()).isNull(); Subchannel subchannel = Iterables.getOnlyElement(subchannels.values()); - int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; + int expectedTimes = PickFirstLoadBalancerProvider.isEnabledNewPickFirst() + && !PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; verify(subchannel, times(expectedTimes)).requestConnection(); verify(helper).updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class)); verify(helper).createSubchannel(any(CreateSubchannelArgs.class)); @@ -184,7 +185,8 @@ public void subchannelNotAutoReconnectAfterReenteringIdle() { pickerCaptor.getValue().pickSubchannel(args); Subchannel subchannel = subchannels.get(Collections.singletonList(childLbState.getEag())); InOrder inOrder = Mockito.inOrder(helper, subchannel); - int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; + int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() + || !PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 2 : 1; inOrder.verify(subchannel, times(expectedTimes)).requestConnection(); deliverSubchannelState(subchannel, CSI_READY); inOrder.verify(helper).updateBalancingState(eq(READY), any(SubchannelPicker.class)); @@ -443,8 +445,10 @@ public void skipFailingHosts_pickNextNonFailingHost() { PickResult result = pickerCaptor.getValue().pickSubchannel(args); assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getSubchannel()).isNull(); // buffer request - int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; // verify kicked off connection to server2 + int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() + || !PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 2 : 1; + verify(getSubChannel(servers.get(1)), times(expectedTimes)).requestConnection(); assertThat(subchannels.size()).isEqualTo(2); // no excessive connection diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 24c2a43b83a..76b92cd8c03 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -737,7 +737,7 @@ public void resolved_simpleCallFailedToRoute_routeWithNonForwardingAction() { ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); assertServiceConfigForLoadBalancingConfig( Collections.singletonList(cluster2), (Map) result.getServiceConfig().getConfig()); @@ -1071,7 +1071,7 @@ public void resolved_simpleCallSucceeds_routeToWeightedCluster() { ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); assertServiceConfigForLoadBalancingConfig( Arrays.asList(cluster1, cluster2), (Map) result.getServiceConfig().getConfig()); assertThat(result.getAttributes().get(InternalXdsAttributes.XDS_CLIENT_POOL)).isNotNull(); @@ -1100,7 +1100,7 @@ public void resolved_simpleCallSucceeds_routeToRls() { ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); @SuppressWarnings("unchecked") Map resultServiceConfig = (Map) result.getServiceConfig().getConfig(); List> rawLbConfigs = @@ -1181,7 +1181,7 @@ public void resolved_simpleCallSucceeds_routeToRls() { private void assertEmptyResolutionResult(String resource) { verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); assertThat((Map) result.getServiceConfig().getConfig()).isEmpty(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); Result configResult = configSelector.selectConfig( @@ -1260,7 +1260,7 @@ private InternalConfigSelector resolveToClusters() { ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); - assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAddressesOrError().getValue()).isEmpty(); assertServiceConfigForLoadBalancingConfig( Arrays.asList(cluster1, cluster2), (Map) result.getServiceConfig().getConfig()); assertThat(result.getAttributes().get(InternalXdsAttributes.XDS_CLIENT_POOL)).isNotNull(); diff --git a/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java index 2c349eec4af..c6b8e7515b2 100644 --- a/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsSecurityClientServerTest.java @@ -43,6 +43,7 @@ import io.grpc.Server; import io.grpc.ServerCredentials; import io.grpc.Status; +import io.grpc.StatusOr; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; @@ -520,7 +521,8 @@ public void refresh() { } void resolved() { - ResolutionResult.Builder builder = ResolutionResult.newBuilder().setAddresses(servers); + ResolutionResult.Builder builder = ResolutionResult.newBuilder() + .setAddressesOrError(StatusOr.fromValue(servers)); listener.onResult(builder.build()); }