diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 5426f83f90bb..f657dccc4052 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,23 +8,24 @@ See [CONTRIBUTING.md](https://github.com/grpc/grpc-community/blob/master/CONTRIB for general contribution guidelines. ## Maintainers (in alphabetical order) - - [dapengzhang0](https://github.com/dapengzhang0), Google LLC - [ejona86](https://github.com/ejona86), Google LLC -- [ericgribkoff](https://github.com/ericgribkoff), Google LLC - [ran-su](https://github.com/ran-su), Google LLC - [sanjaypujare](https://github.com/sanjaypujare), Google LLC - [sergiitk](https://github.com/sergiitk), Google LLC - [srini100](https://github.com/srini100), Google LLC -- [voidzcy](https://github.com/voidzcy), Google LLC +- [temawi](https://github.com/temawi), Google LLC +- [YifeiZhuang](https://github.com/YifeiZhuang), Google LLC - [zhangkun83](https://github.com/zhangkun83), Google LLC ## Emeritus Maintainers (in alphabetical order) - [carl-mastrangelo](https://github.com/carl-mastrangelo), Google LLC - [creamsoup](https://github.com/creamsoup), Google LLC +- [ericgribkoff](https://github.com/ericgribkoff), Google LLC - [jiangtaoli2016](https://github.com/jiangtaoli2016), Google LLC - [jtattermusch](https://github.com/jtattermusch), Google LLC - [louiscryan](https://github.com/louiscryan), Google LLC - [nicolasnoble](https://github.com/nicolasnoble), Google LLC - [nmittler](https://github.com/nmittler), Google LLC +- [voidzcy](https://github.com/voidzcy), Google LLC - [zpencer](https://github.com/zpencer), Google LLC diff --git a/README.md b/README.md index ea11cbde260c..d11c28508050 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,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.41.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.41.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.42.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.42.1/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -43,17 +43,17 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.41.0 + 1.42.1 io.grpc grpc-protobuf - 1.41.0 + 1.42.1 io.grpc grpc-stub - 1.41.0 + 1.42.1 org.apache.tomcat @@ -65,23 +65,23 @@ 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 -implementation 'io.grpc:grpc-netty-shaded:1.41.0' -implementation 'io.grpc:grpc-protobuf:1.41.0' -implementation 'io.grpc:grpc-stub:1.41.0' +implementation 'io.grpc:grpc-netty-shaded:1.42.1' +implementation 'io.grpc:grpc-protobuf:1.42.1' +implementation 'io.grpc:grpc-stub:1.42.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.41.0' -implementation 'io.grpc:grpc-protobuf-lite:1.41.0' -implementation 'io.grpc:grpc-stub:1.41.0' +implementation 'io.grpc:grpc-okhttp:1.42.1' +implementation 'io.grpc:grpc-protobuf-lite:1.42.1' +implementation 'io.grpc:grpc-stub:1.42.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.41.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.42.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -111,9 +111,9 @@ For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin 0.6.1 - com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.17.2:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.41.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.42.1:exe:${os.detected.classifier} @@ -139,11 +139,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.17.3" + artifact = "com.google.protobuf:protoc:3.17.2" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.42.1' } } generateProtoTasks { @@ -172,11 +172,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.17.3" + artifact = "com.google.protobuf:protoc:3.17.2" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.42.1' } } generateProtoTasks { diff --git a/RELEASING.md b/RELEASING.md index f16cb37c3ad4..f2e37f312b42 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -10,19 +10,6 @@ We deploy GRPC to Maven Central under the following systems: Other systems may also work, but we haven't verified them. -Prerequisites -------------- - -### Set Up OSSRH Account - -If you haven't deployed artifacts to Maven Central before, you need to setup -your OSSRH (OSS Repository Hosting) account. -- Follow the instructions on [this - page](https://central.sonatype.org/pages/ossrh-guide.html) to set up an - account with OSSRH. - - You only need to create the account, not set up a new project - - Contact a gRPC maintainer to add your account after you have created it. - Common Variables ---------------- Many of the following commands expect release-specific variables to be set. Set diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java index b7d79435f005..fe8c005da452 100644 --- a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java +++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java @@ -44,30 +44,65 @@ private GoogleDefaultChannelCredentials() {} * as fallback. */ public static ChannelCredentials create() { - ChannelCredentials nettyCredentials = - InternalNettyChannelCredentials.create(createClientFactory()); - CallCredentials callCredentials; - try { - callCredentials = MoreCallCredentials.from(GoogleCredentials.getApplicationDefault()); - } catch (IOException e) { - callCredentials = new FailingCallCredentials( - Status.UNAUTHENTICATED - .withDescription("Failed to get Google default credentials") - .withCause(e)); - } - return CompositeChannelCredentials.create(nettyCredentials, callCredentials); + return newBuilder().build(); } - private static InternalProtocolNegotiator.ClientFactory createClientFactory() { - SslContext sslContext; - try { - sslContext = GrpcSslContexts.forClient().build(); - } catch (SSLException e) { - throw new RuntimeException(e); + /** + * Returns a new instance of {@link Builder}. + * + * @since 1.42.0 + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder for {@link GoogleDefaultChannelCredentials} instances. + * + * @since 1.42.0 + */ + public static final class Builder { + private CallCredentials callCredentials; + + private Builder() {} + + /** Constructs GoogleDefaultChannelCredentials with a given call credential. */ + public Builder callCredentials(CallCredentials callCreds) { + callCredentials = callCreds; + return this; + } + + /** Builds a GoogleDefaultChannelCredentials instance. */ + public ChannelCredentials build() { + ChannelCredentials nettyCredentials = + InternalNettyChannelCredentials.create(createClientFactory()); + if (callCredentials != null) { + return CompositeChannelCredentials.create(nettyCredentials, callCredentials); + } + CallCredentials callCreds; + try { + callCreds = MoreCallCredentials.from(GoogleCredentials.getApplicationDefault()); + } catch (IOException e) { + callCreds = + new FailingCallCredentials( + Status.UNAUTHENTICATED + .withDescription("Failed to get Google default credentials") + .withCause(e)); + } + return CompositeChannelCredentials.create(nettyCredentials, callCreds); + } + + private static InternalProtocolNegotiator.ClientFactory createClientFactory() { + SslContext sslContext; + try { + sslContext = GrpcSslContexts.forClient().build(); + } catch (SSLException e) { + throw new RuntimeException(e); + } + return new GoogleDefaultProtocolNegotiatorFactory( + /* targetServiceAccounts= */ ImmutableList.of(), + SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), + sslContext); } - return new GoogleDefaultProtocolNegotiatorFactory( - /* targetServiceAccounts= */ ImmutableList.of(), - SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), - sslContext); } } diff --git a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java index dd9cf26bd3d0..41ea76146de2 100644 --- a/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java +++ b/binder/src/androidTest/java/io/grpc/binder/BinderChannelSmokeTest.java @@ -36,11 +36,13 @@ import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.NameResolverRegistry; import io.grpc.Server; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptors; import io.grpc.ServerServiceDefinition; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.testing.FakeNameResolverProvider; import io.grpc.stub.ClientCalls; import io.grpc.stub.ServerCalls; import io.grpc.stub.StreamObserver; @@ -66,6 +68,7 @@ public final class BinderChannelSmokeTest { private static final int SLIGHTLY_MORE_THAN_ONE_BLOCK = 16 * 1024 + 100; private static final String MSG = "Some text which will be repeated many many times"; + private static final String SERVER_TARGET_URI = "fake://server"; final MethodDescriptor method = MethodDescriptor.newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE) @@ -85,7 +88,7 @@ public final class BinderChannelSmokeTest { .setType(MethodDescriptor.MethodType.BIDI_STREAMING) .build(); - AndroidComponentAddress serverAddress; + FakeNameResolverProvider fakeNameResolverProvider; ManagedChannel channel; AtomicReference headersCapture = new AtomicReference<>(); @@ -118,6 +121,8 @@ public void setUp() throws Exception { TestUtils.recordRequestHeadersInterceptor(headersCapture)); AndroidComponentAddress serverAddress = HostServices.allocateService(appContext); + fakeNameResolverProvider = new FakeNameResolverProvider(SERVER_TARGET_URI, serverAddress); + NameResolverRegistry.getDefaultRegistry().register(fakeNameResolverProvider); HostServices.configureService(serverAddress, HostServices.serviceParamsBuilder() .setServerFactory((service, receiver) -> @@ -132,6 +137,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { channel.shutdownNow(); + NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverProvider); HostServices.awaitServiceShutdown(); } @@ -192,6 +198,12 @@ public void testStreamingCallOptionHeaders() throws Exception { assertThat(headersCapture.get().get(GrpcUtil.TIMEOUT_KEY)).isGreaterThan(0); } + @Test + public void testConnectViaTargetUri() throws Exception { + channel = BinderChannelBuilder.forTarget(SERVER_TARGET_URI, appContext).build(); + assertThat(doCall("Hello").get()).isEqualTo("Hello"); + } + private static String createLargeString(int size) { StringBuilder sb = new StringBuilder(); while (sb.length() < size) { diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java index 99191cfad3c6..91e4e8f1c76a 100644 --- a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java +++ b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java @@ -67,13 +67,35 @@ public final class BinderChannelBuilder *

You the caller are responsible for managing the lifecycle of any channels built by the * resulting builder. They will not be shut down automatically. * - * @param targetAddress the {@link AndroidComponentAddress} referencing the service to bind to. + * @param directAddress the {@link AndroidComponentAddress} referencing the service to bind to. * @param sourceContext the context to bind from (e.g. The current Activity or Application). * @return a new builder */ public static BinderChannelBuilder forAddress( - AndroidComponentAddress targetAddress, Context sourceContext) { - return new BinderChannelBuilder(targetAddress, sourceContext); + AndroidComponentAddress directAddress, Context sourceContext) { + return new BinderChannelBuilder( + checkNotNull(directAddress, "directAddress"), null, sourceContext); + } + + /** + * Creates a channel builder that will bind to a remote Android service, via a string + * target name which will be resolved. + * + *

The underlying Android binding will be torn down when the channel becomes idle. This happens + * after 30 minutes without use by default but can be configured via {@link + * ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link + * ManagedChannel#enterIdle()}. + * + *

You the caller are responsible for managing the lifecycle of any channels built by the + * resulting builder. They will not be shut down automatically. + * + * @param target A target uri which should resolve into an {@link AndroidComponentAddress} + * referencing the service to bind to. + * @param sourceContext the context to bind from (e.g. The current Activity or Application). + * @return a new builder + */ + public static BinderChannelBuilder forTarget(String target, Context sourceContext) { + return new BinderChannelBuilder(null, checkNotNull(target, "target"), sourceContext); } /** @@ -88,7 +110,7 @@ public static BinderChannelBuilder forAddress(String name, int port) { /** * Always fails. Call {@link #forAddress(AndroidComponentAddress, Context)} instead. */ - @DoNotCall("Unsupported. Use forAddress(AndroidComponentAddress, Context) instead") + @DoNotCall("Unsupported. Use forTarget(String, Context) instead") public static BinderChannelBuilder forTarget(String target) { throw new UnsupportedOperationException( "call forAddress(AndroidComponentAddress, Context) instead"); @@ -104,9 +126,11 @@ public static BinderChannelBuilder forTarget(String target) { private BindServiceFlags bindServiceFlags; private BinderChannelBuilder( - AndroidComponentAddress targetAddress, + @Nullable AndroidComponentAddress directAddress, + @Nullable String target, Context sourceContext) { - mainThreadExecutor = ContextCompat.getMainExecutor(sourceContext); + mainThreadExecutor = + ContextCompat.getMainExecutor(checkNotNull(sourceContext, "sourceContext")); securityPolicy = SecurityPolicies.internalOnly(); inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT; bindServiceFlags = BindServiceFlags.DEFAULTS; @@ -126,12 +150,20 @@ public ClientTransportFactory buildClientTransportFactory() { } } - managedChannelImplBuilder = - new ManagedChannelImplBuilder( - targetAddress, - targetAddress.getAuthority(), - new BinderChannelTransportFactoryBuilder(), - null); + if (directAddress != null) { + managedChannelImplBuilder = + new ManagedChannelImplBuilder( + directAddress, + directAddress.getAuthority(), + new BinderChannelTransportFactoryBuilder(), + null); + } else { + managedChannelImplBuilder = + new ManagedChannelImplBuilder( + target, + new BinderChannelTransportFactoryBuilder(), + null); + } } @Override diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java index be46b9e3e54c..dcf36be00ca6 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java @@ -16,9 +16,20 @@ package io.grpc.binder; +import android.annotation.SuppressLint; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.Build; import android.os.Process; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import io.grpc.ExperimentalApi; import io.grpc.Status; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import javax.annotation.CheckReturnValue; /** Static factory methods for creating standard security policies. */ @@ -55,4 +66,125 @@ public Status checkAuthorization(int uid) { } }; } + + /** + * Creates a {@link SecurityPolicy} which checks if the package signature + * matches {@code requiredSignature}. + * + * @param packageName the package name of the allowed package. + * @param requiredSignature the allowed signature of the allowed package. + * @throws NullPointerException if any of the inputs are {@code null}. + */ + public static SecurityPolicy hasSignature( + PackageManager packageManager, String packageName, Signature requiredSignature) { + return oneOfSignatures( + packageManager, packageName, ImmutableList.of(requiredSignature)); + } + + /** + * Creates a {@link SecurityPolicy} which checks if the package signature + * matches any of {@code requiredSignatures}. + * + * @param packageName the package name of the allowed package. + * @param requiredSignatures the allowed signatures of the allowed package. + * @throws NullPointerException if any of the inputs are {@code null}. + * @throws IllegalArgumentException if {@code requiredSignatures} is empty. + */ + public static SecurityPolicy oneOfSignatures( + PackageManager packageManager, + String packageName, + Collection requiredSignatures) { + Preconditions.checkNotNull(packageManager, "packageManager"); + Preconditions.checkNotNull(packageName, "packageName"); + Preconditions.checkNotNull(requiredSignatures, "requiredSignatures"); + Preconditions.checkArgument(!requiredSignatures.isEmpty(), + "requiredSignatures"); + ImmutableList requiredSignaturesImmutable = ImmutableList.copyOf(requiredSignatures); + + for (Signature requiredSignature : requiredSignaturesImmutable) { + Preconditions.checkNotNull(requiredSignature); + } + + return new SecurityPolicy() { + @Override + public Status checkAuthorization(int uid) { + return checkUidSignature( + packageManager, uid, packageName, requiredSignaturesImmutable); + } + }; + } + + private static Status checkUidSignature( + PackageManager packageManager, + int uid, + String packageName, + ImmutableList requiredSignatures) { + String[] packages = packageManager.getPackagesForUid(uid); + if (packages == null) { + return Status.UNAUTHENTICATED.withDescription( + "Rejected by signature check security policy"); + } + boolean packageNameMatched = false; + for (String pkg : packages) { + if (!packageName.equals(pkg)) { + continue; + } + packageNameMatched = true; + if (checkPackageSignature(packageManager, pkg, requiredSignatures)) { + return Status.OK; + } + } + return Status.PERMISSION_DENIED.withDescription( + "Rejected by signature check security policy. Package name matched: " + + packageNameMatched); + } + + /** + * Checks if the signature of {@code packageName} matches one of the given signatures. + * + * @param packageName the package to be checked + * @param requiredSignatures list of signatures. + * @return {@code true} if {@code packageName} has a matching signature. + */ + @SuppressWarnings("deprecation") // For PackageInfo.signatures + @SuppressLint("PackageManagerGetSignatures") // We only allow 1 signature. + private static boolean checkPackageSignature( + PackageManager packageManager, + String packageName, + ImmutableList requiredSignatures) { + PackageInfo packageInfo; + try { + if (Build.VERSION.SDK_INT >= 28) { + packageInfo = + packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES); + if (packageInfo.signingInfo == null) { + return false; + } + Signature[] signatures = + packageInfo.signingInfo.hasMultipleSigners() + ? packageInfo.signingInfo.getApkContentsSigners() + : packageInfo.signingInfo.getSigningCertificateHistory(); + + for (Signature signature : signatures) { + if (requiredSignatures.contains(signature)) { + return true; + } + } + } else { + packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + if (packageInfo.signatures == null || packageInfo.signatures.length != 1) { + // Reject multiply-signed apks because of b/13678484 + // (See PackageManagerGetSignatures supression above). + return false; + } + + if (requiredSignatures.contains(packageInfo.signatures[0])) { + return true; + } + } + } catch (NameNotFoundException nnfe) { + return false; + } + return false; + } } diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicy.java b/binder/src/main/java/io/grpc/binder/SecurityPolicy.java index d7dad53fdc83..d13f3a863fd2 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicy.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicy.java @@ -23,6 +23,11 @@ /** * Decides whether a given Android UID is authorized to access some resource. * + * While it's possible to extend this class to define your own policy, it's strongly + * recommended that you only use the policies provided by the {@link SecurityPolicies} or + * {@link UntrustedSecurityPolicies} classes. Implementing your own security policy requires + * significant care, and an understanding of the details and pitfalls of Android security. + * *

IMPORTANT For any concrete extensions of this class, it's assumed that the * authorization status of a given UID will not change as long as a process with that UID is * alive. diff --git a/binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java b/binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java new file mode 100644 index 000000000000..7c842b025acb --- /dev/null +++ b/binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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.binder; + +import io.grpc.ExperimentalApi; +import io.grpc.Status; +import javax.annotation.CheckReturnValue; + +/** + * Static factory methods for creating untrusted security policies. + */ +@CheckReturnValue +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") +public final class UntrustedSecurityPolicies { + + private UntrustedSecurityPolicies() {} + + /** + * Return a security policy which allows any peer on device. + * Servers should only use this policy if they intend to expose + * a service to all applications on device. + * Clients should only use this policy if they don't need to trust the + * application they're connecting to. + */ + public static SecurityPolicy untrustedPublic() { + return new SecurityPolicy() { + @Override + public Status checkAuthorization(int uid) { + return Status.OK; + } + }; + } +} diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java index 6fd9e22ebaac..86edb5ad7df2 100644 --- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java +++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java @@ -17,22 +17,64 @@ package io.grpc.binder; import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; import android.os.Process; +import androidx.test.core.app.ApplicationProvider; +import com.google.common.collect.ImmutableList; import io.grpc.Status; +import io.grpc.binder.SecurityPolicy; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public final class SecurityPoliciesTest { + private static final int MY_UID = Process.myUid(); private static final int OTHER_UID = MY_UID + 1; + private static final int OTHER_UID_SAME_SIGNATURE = MY_UID + 2; + private static final int OTHER_UID_NO_SIGNATURE = MY_UID + 3; + private static final int OTHER_UID_UNKNOWN = MY_UID + 4; private static final String PERMISSION_DENIED_REASONS = "some reasons"; + private static final Signature SIG1 = new Signature("1234"); + private static final Signature SIG2 = new Signature("4321"); + + private static final String OTHER_UID_PACKAGE_NAME = "other.package"; + private static final String OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME = "other.package.samesignature"; + private static final String OTHER_UID_NO_SIGNATURE_PACKAGE_NAME = "other.package.nosignature"; + + private Context appContext; + private PackageManager packageManager; + private SecurityPolicy policy; + @Before + public void setUp() { + appContext = ApplicationProvider.getApplicationContext(); + packageManager = appContext.getPackageManager(); + installPackage(MY_UID, appContext.getPackageName(), SIG1); + installPackage(OTHER_UID, OTHER_UID_PACKAGE_NAME, SIG2); + installPackage(OTHER_UID_SAME_SIGNATURE, OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME, SIG1); + installPackage(OTHER_UID_NO_SIGNATURE, OTHER_UID_NO_SIGNATURE_PACKAGE_NAME); + } + + @SuppressWarnings("deprecation") + private void installPackage(int uid, String packageName, Signature... signatures) { + PackageInfo info = new PackageInfo(); + info.packageName = packageName; + info.signatures = signatures; + shadowOf(packageManager).installPackage(info); + shadowOf(packageManager).setPackagesForUid(uid, packageName); + } + @Test public void testInternalOnly() throws Exception { policy = SecurityPolicies.internalOnly(); @@ -53,4 +95,80 @@ public void testPermissionDenied() throws Exception { assertThat(policy.checkAuthorization(OTHER_UID).getDescription()) .isEqualTo(PERMISSION_DENIED_REASONS); } + + @Test + public void testHasSignature_succeedsIfPackageNameAndSignaturesMatch() + throws Exception { + policy = SecurityPolicies.hasSignature(packageManager, OTHER_UID_PACKAGE_NAME, SIG2); + + // THEN UID for package that has SIG2 will be authorized + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + } + + @Test + public void testHasSignature_failsIfPackageNameDoesNotMatch() throws Exception { + policy = SecurityPolicies.hasSignature(packageManager, appContext.getPackageName(), SIG1); + + // THEN UID for package that has SIG1 but different package name will not be authorized + assertThat(policy.checkAuthorization(OTHER_UID_SAME_SIGNATURE).getCode()) + .isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + public void testHasSignature_failsIfSignatureDoesNotMatch() throws Exception { + policy = SecurityPolicies.hasSignature(packageManager, OTHER_UID_PACKAGE_NAME, SIG1); + + // THEN UID for package that doesn't have SIG1 will not be authorized + assertThat(policy.checkAuthorization(OTHER_UID).getCode()) + .isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + public void testOneOfSignatures_succeedsIfPackageNameAndSignaturesMatch() + throws Exception { + policy = + SecurityPolicies.oneOfSignatures( + packageManager, OTHER_UID_PACKAGE_NAME, ImmutableList.of(SIG2)); + + // THEN UID for package that has SIG2 will be authorized + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + } + + @Test + public void testOneOfSignature_failsIfAllSignaturesDoNotMatch() throws Exception { + policy = + SecurityPolicies.oneOfSignatures( + packageManager, + appContext.getPackageName(), + ImmutableList.of(SIG1, new Signature("1314"))); + + // THEN UID for package that has SIG1 but different package name will not be authorized + assertThat(policy.checkAuthorization(OTHER_UID_SAME_SIGNATURE).getCode()) + .isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + public void testOneOfSignature_succeedsIfPackageNameAndOneOfSignaturesMatch() + throws Exception { + policy = + SecurityPolicies.oneOfSignatures( + packageManager, + OTHER_UID_PACKAGE_NAME, + ImmutableList.of(SIG1, SIG2)); + + // THEN UID for package that has SIG2 will be authorized + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + } + + @Test + public void testHasSignature_failsIfUidUnknown() throws Exception { + policy = + SecurityPolicies.hasSignature( + packageManager, + appContext.getPackageName(), + SIG1); + + assertThat(policy.checkAuthorization(OTHER_UID_UNKNOWN).getCode()) + .isEqualTo(Status.UNAUTHENTICATED.getCode()); + } } diff --git a/buildscripts/kokoro/xds-k8s.cfg b/buildscripts/kokoro/psm-security.cfg similarity index 75% rename from buildscripts/kokoro/xds-k8s.cfg rename to buildscripts/kokoro/psm-security.cfg index 09a8e705a4d2..f2cfd7babff3 100644 --- a/buildscripts/kokoro/xds-k8s.cfg +++ b/buildscripts/kokoro/psm-security.cfg @@ -1,8 +1,8 @@ # Config file for internal CI # Location of the continuous shell script in repository. -build_file: "grpc-java/buildscripts/kokoro/xds-k8s.sh" -timeout_mins: 120 +build_file: "grpc-java/buildscripts/kokoro/psm-security.sh" +timeout_mins: 180 action { define_artifacts { diff --git a/buildscripts/kokoro/xds-k8s.sh b/buildscripts/kokoro/psm-security.sh similarity index 99% rename from buildscripts/kokoro/xds-k8s.sh rename to buildscripts/kokoro/psm-security.sh index d0275d459e30..105e67b2d0fd 100755 --- a/buildscripts/kokoro/xds-k8s.sh +++ b/buildscripts/kokoro/psm-security.sh @@ -168,6 +168,7 @@ main() { cd "${TEST_DRIVER_FULL_DIR}" run_test baseline_test run_test security_test + run_test authz_test } main "$@" diff --git a/buildscripts/kokoro/upload_artifacts.sh b/buildscripts/kokoro/upload_artifacts.sh index 8d7f2f5b3656..20e16c38a2ff 100644 --- a/buildscripts/kokoro/upload_artifacts.sh +++ b/buildscripts/kokoro/upload_artifacts.sh @@ -33,6 +33,8 @@ LOCAL_OTHER_ARTIFACTS="$KOKORO_GFILE_DIR"/github/grpc-java/artifacts/ # from macos job: [[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-osx-x86_64.exe' | wc -l)" != '0' ]] +# copy all x86 artifacts to aarch until native artifacts are built +find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-osx-x86_64.exe*' -exec bash -c 'cp "${0}" "${0/x86/aarch}"' {} \; # from windows job: [[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-windows-x86_64.exe' | wc -l)" != '0' ]] diff --git a/cronet/README.md b/cronet/README.md index 8b220bd606de..a682c5bcee8f 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.41.0' +implementation 'io.grpc:grpc-cronet:1.42.1' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 60e3bb35a85d..d9541c566231 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.41.0' -implementation 'io.grpc:grpc-okhttp:1.41.0' +implementation 'io.grpc:grpc-android:1.42.1' +implementation 'io.grpc:grpc-okhttp:1.42.1' ``` You also need permission to access the device's network state in your diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java index 8607d3996a5d..1eebaa63a8e8 100644 --- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java +++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java @@ -287,8 +287,9 @@ void handleAddresses( cancelLbRpcRetryTimer(); startLbRpc(); } - // Start the fallback timer if it's never started - if (fallbackTimer == null) { + // Start the fallback timer if it's never started and we are not already using fallback + // backends. + if (fallbackTimer == null && !usingFallbackBackends) { fallbackTimer = syncContext.schedule( new FallbackModeTask(BALANCER_TIMEOUT_STATUS), FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS, timerService); diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java index cb231c6c055f..293c0aa0b82a 100644 --- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java +++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java @@ -1462,6 +1462,33 @@ public void grpclbFallback_noBalancerAddress() { .updateBalancingState(eq(TRANSIENT_FAILURE), any(SubchannelPicker.class)); } + /** + * A test for a situation where we first only get backend addresses resolved and then in a + * later name resolution get both backend and load balancer addresses. The first instance + * will switch us to using fallback backends and it is important that in the second instance + * we do not start a fallback timer as it will fail when it triggers if the fallback backends + * are already in use. + */ + @Test + public void grpclbFallback_noTimerWhenAlreadyInFallback() { + // Initially we only get backend addresses without any LB ones. This should get us to use + // fallback backends from the start as we won't be able to even talk to the load balancer. + // No fallback timer would be started as we already started to use fallback backends. + deliverResolvedAddresses(createResolvedBalancerAddresses(1), + Collections.emptyList()); + assertEquals(0, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER)); + + // Later a new name resolution call happens and we get both backend and LB addresses. Since we + // are already operating with fallback backends a fallback timer should not be started to move + // us to fallback mode. + deliverResolvedAddresses(Collections.emptyList(), + createResolvedBalancerAddresses(1)); + + // If a fallback timer is started it will eventually throw an exception when it tries to switch + // us to using fallback backends when we already are using them. + assertEquals(0, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER)); + } + @Test public void grpclbFallback_balancerLost() { subtestGrpclbFallbackConnectionLost(true, false); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java b/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java index b2af083bd248..6b3e7213cfb7 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java @@ -180,16 +180,16 @@ private void start() throws Exception { .addService( ServerInterceptors.intercept( new TestServiceImpl(serverId, host), new TestInfoInterceptor(host))) - .build() - .start(); + .build(); + server.start(); maintenanceServer = NettyServerBuilder.forPort(maintenancePort) .addService(new XdsUpdateHealthServiceImpl(health)) .addService(health.getHealthService()) .addService(ProtoReflectionService.newInstance()) .addServices(AdminInterface.getStandardServices()) - .build() - .start(); + .build(); + maintenanceServer.start(); } else { server = NettyServerBuilder.forPort(port) @@ -200,8 +200,8 @@ private void start() throws Exception { .addService(health.getHealthService()) .addService(ProtoReflectionService.newInstance()) .addServices(AdminInterface.getStandardServices()) - .build() - .start(); + .build(); + server.start(); maintenanceServer = null; } health.setStatus("", ServingStatus.SERVING); diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java index c286c17f6409..f552b937a05c 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java @@ -109,6 +109,9 @@ class NettyServerHandler extends AbstractNettyHandler { @VisibleForTesting static final long GRACEFUL_SHUTDOWN_PING = 0x97ACEF001L; private static final long GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10); + /** Temporary workaround for #8674. Fine to delete after v1.45 release, and maybe earlier. */ + private static final boolean DISABLE_CONNECTION_HEADER_CHECK = Boolean.parseBoolean( + System.getProperty("io.grpc.netty.disableConnectionHeaderCheck", "false")); private final Http2Connection.PropertyKey streamKey; private final ServerTransportListener transportListener; @@ -380,7 +383,7 @@ private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers try { // Connection-specific header fields makes a request malformed. Ideally this would be handled // by Netty. RFC 7540 section 8.1.2.2 - if (headers.contains(CONNECTION)) { + if (!DISABLE_CONNECTION_HEADER_CHECK && headers.contains(CONNECTION)) { resetStream(ctx, streamId, Http2Error.PROTOCOL_ERROR.code(), ctx.newPromise()); return; } diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index af5ebe2886cc..b3e90d158fa8 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -105,15 +105,22 @@ private enum NegotiationType { new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .cipherSuites( // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS. - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) - .tlsVersions(TlsVersion.TLS_1_2) + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + + // TLS 1.3 does not work so far. See issues: + // https://github.com/grpc/grpc-java/issues/7765 + // + // TLS 1.3 + //CipherSuite.TLS_AES_128_GCM_SHA256, + //CipherSuite.TLS_AES_256_GCM_SHA384, + //CipherSuite.TLS_CHACHA20_POLY1305_SHA256 + ) + .tlsVersions(/*TlsVersion.TLS_1_3,*/ TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build(); @@ -396,6 +403,32 @@ public OkHttpChannelBuilder connectionSpec( return this; } + /** + * Sets the connection specification used for secure connections. + * + *

By default a modern, HTTP/2-compatible spec will be used. + * + *

This method is only used when building a secure connection. For plaintext + * connection, use {@link #usePlaintext()} instead. + * + * @param tlsVersions List of tls versions. + * @param cipherSuites List of cipher suites. + */ + public OkHttpChannelBuilder tlsConnectionSpec( + String[] tlsVersions, String[] cipherSuites) { + Preconditions.checkState(!freezeSecurityConfiguration, + "Cannot change security when using ChannelCredentials"); + Preconditions.checkNotNull(tlsVersions, "tls versions must not null"); + Preconditions.checkNotNull(cipherSuites, "ciphers must not null"); + + this.connectionSpec = new ConnectionSpec.Builder(true) + .supportsTlsExtensions(true) + .tlsVersions(tlsVersions) + .cipherSuites(cipherSuites) + .build(); + return this; + } + /** Sets the negotiation type for the HTTP/2 connection to plaintext. */ @Override public OkHttpChannelBuilder usePlaintext() { diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java index 70fa796f2488..1a9aab284bbd 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java @@ -354,6 +354,22 @@ public enum CipherSuite { // TLS_ECDHE_ECDSA_WITH_AES_256_CCM("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad, 7251, MAX_VALUE, MAX_VALUE), // TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae, 7251, MAX_VALUE, MAX_VALUE), // TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af, 7251, MAX_VALUE, MAX_VALUE), + + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", 0xcca8), + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", 0xcca9), + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256("TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", 0xccaa), + // TLS_PSK_WITH_CHACHA20_POLY1305_SHA256("TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccab), + TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccac), + // TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256("TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccad), + // TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256("TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccae), + + // TLS 1.3 https://tools.ietf.org/html/rfc8446 + TLS_AES_128_GCM_SHA256("TLS_AES_128_GCM_SHA256", 0x1301), + TLS_AES_256_GCM_SHA384("TLS_AES_256_GCM_SHA384", 0x1302), + TLS_CHACHA20_POLY1305_SHA256("TLS_CHACHA20_POLY1305_SHA256", 0x1303), + TLS_AES_128_CCM_SHA256("TLS_AES_128_CCM_SHA256", 0x1304), + TLS_AES_128_CCM_8_SHA256("TLS_AES_128_CCM_8_SHA256", 0x1305), + ; final String javaName; @@ -372,6 +388,12 @@ private CipherSuite( this.javaName = javaName; } + @SuppressWarnings("UnusedVariable") + private CipherSuite( + String javaName, int value) { + this.javaName = javaName; + } + public static CipherSuite forJavaName(String javaName) { return javaName.startsWith("SSL_") ? valueOf("TLS_" + javaName.substring(4)) diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java index 457e9c301f37..b84a1ff94eec 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java @@ -30,40 +30,43 @@ */ public final class ConnectionSpec { - // This is a subset of the cipher suites supported in Chrome 37, current as of 2014-10-5. - // All of these suites are available on Android 5.0; earlier releases support a subset of - // these suites. https://github.com/square/okhttp/issues/330 + // This is nearly equal to the cipher suites supported in Chrome 72, current as of 2019-02-24. + // See https://tinyurl.com/okhttp-cipher-suites for availability. private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] { - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - - // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll - // continue to include them until better suites are commonly available. For example, none - // of the better cipher suites listed above shipped with Android 4.4 or Java 7. - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, - CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + // TLSv1.3. + CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_AES_256_GCM_SHA384, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256, + + // TLSv1.0, TLSv1.1, TLSv1.2. + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + + // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll + // continue to include them until better suites are commonly available. + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA }; /** A modern TLS connection with extensions like SNI and ALPN available. */ public static final ConnectionSpec MODERN_TLS = new Builder(true) .cipherSuites(APPROVED_CIPHER_SUITES) - .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) + .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build(); /** A backwards-compatible fallback connection for interop with obsolete servers. */ public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_0) + .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) .supportsTlsExtensions(true) .build(); diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java index 548f4acbc5b3..6692c80fad4d 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java @@ -26,6 +26,7 @@ * {@link SSLSocket#setEnabledProtocols}. */ public enum TlsVersion { + TLS_1_3("TLSv1.3"), // 2016. TLS_1_2("TLSv1.2"), // 2008. TLS_1_1("TLSv1.1"), // 2006. TLS_1_0("TLSv1"), // 1999. @@ -39,7 +40,9 @@ private TlsVersion(String javaName) { } public static TlsVersion forJavaName(String javaName) { - if ("TLSv1.2".equals(javaName)) { + if ("TLSv1.3".equals(javaName)) { + return TLS_1_3; + } else if ("TLSv1.2".equals(javaName)) { return TLS_1_2; } else if ("TLSv1.1".equals(javaName)) { return TLS_1_1; diff --git a/rls/src/main/java/io/grpc/rls/RlsProtoData.java b/rls/src/main/java/io/grpc/rls/RlsProtoData.java index 9e62f228a1f8..2ddf00db207b 100644 --- a/rls/src/main/java/io/grpc/rls/RlsProtoData.java +++ b/rls/src/main/java/io/grpc/rls/RlsProtoData.java @@ -24,15 +24,13 @@ import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.grpc.Internal; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** RlsProtoData is a collection of internal representation of RouteLookupService proto messages. */ -@Internal -public final class RlsProtoData { +final class RlsProtoData { private RlsProtoData() {} @@ -137,7 +135,7 @@ public String toString() { /** A config object for gRPC RouteLookupService. */ @Immutable - public static final class RouteLookupConfig { + static final class RouteLookupConfig { private final ImmutableList grpcKeyBuilders; @@ -154,8 +152,7 @@ public static final class RouteLookupConfig { @Nullable private final String defaultTarget; - /** Constructor. */ - public RouteLookupConfig( + RouteLookupConfig( List grpcKeyBuilders, String lookupService, long lookupServiceTimeoutInNanos, @@ -284,14 +281,13 @@ public String toString() { * is true, one of the specified names must be present for the keybuilder to match. */ @Immutable - public static final class NameMatcher { + static final class NameMatcher { private final String key; private final ImmutableList names; - /** Constructor. */ - public NameMatcher(String key, List names) { + NameMatcher(String key, List names) { this.key = checkNotNull(key, "key"); this.names = ImmutableList.copyOf(checkNotNull(names, "names")); } @@ -334,7 +330,7 @@ public String toString() { } /** GrpcKeyBuilder is a configuration to construct headers consumed by route lookup service. */ - public static final class GrpcKeyBuilder { + static final class GrpcKeyBuilder { private final ImmutableList names; @@ -343,7 +339,7 @@ public static final class GrpcKeyBuilder { private final ImmutableMap constantKeys; /** Constructor. All args should be nonnull. Headers should head unique keys. */ - public GrpcKeyBuilder( + GrpcKeyBuilder( List names, List headers, ExtraKeys extraKeys, Map constantKeys) { checkState(names != null && !names.isEmpty(), "names cannot be empty"); @@ -414,7 +410,7 @@ public String toString() { * required and includes the proto package name. The method name may be omitted, in which case * any method on the given service is matched. */ - public static final class Name { + static final class Name { private final String service; @@ -425,7 +421,7 @@ public Name(String service) { } /** The primary constructor. */ - public Name(String service, String method) { + Name(String service, String method) { this.service = service; this.method = method; } @@ -467,7 +463,7 @@ public String toString() { } @AutoValue - public abstract static class ExtraKeys { + abstract static class ExtraKeys { static final ExtraKeys DEFAULT = create(null, null, null); @Nullable abstract String host(); @@ -476,7 +472,7 @@ public abstract static class ExtraKeys { @Nullable abstract String method(); - public static ExtraKeys create( + static ExtraKeys create( @Nullable String host, @Nullable String service, @Nullable String method) { return new AutoValue_RlsProtoData_ExtraKeys(host, service, method); } diff --git a/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java b/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java new file mode 100644 index 000000000000..d056707b7196 --- /dev/null +++ b/testing/src/main/java/io/grpc/internal/testing/FakeNameResolverProvider.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021 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.testing; + +import com.google.common.collect.ImmutableList; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; +import io.grpc.Status; +import java.net.SocketAddress; +import java.net.URI; + +/** A name resolver to always resolve the given URI into the given address. */ +public final class FakeNameResolverProvider extends NameResolverProvider { + + private final URI targetUri; + private final SocketAddress address; + + public FakeNameResolverProvider(String targetUri, SocketAddress address) { + this.targetUri = URI.create(targetUri); + this.address = address; + } + + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + if (targetUri.equals(this.targetUri)) { + return new FakeNameResolver(address); + } + return null; + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; // Default + } + + @Override + public String getDefaultScheme() { + return targetUri.getScheme(); + } + + /** A single name resolver. */ + private static final class FakeNameResolver extends NameResolver { + private static final String AUTHORITY = "fake-authority"; + + private final SocketAddress address; + private volatile boolean shutdown; + + private FakeNameResolver(SocketAddress address) { + this.address = address; + } + + @Override + public void start(Listener2 listener) { + if (shutdown) { + listener.onError(Status.FAILED_PRECONDITION.withDescription("Resolver is shutdown")); + } else { + listener.onResult( + ResolutionResult.newBuilder() + .setAddresses(ImmutableList.of(new EquivalentAddressGroup(address))) + .build()); + } + } + + @Override + public String getServiceAuthority() { + return AUTHORITY; + } + + @Override + public void shutdown() { + shutdown = true; + } + } +} diff --git a/xds/build.gradle b/xds/build.gradle index a8dbde3e0a11..16b1c8779062 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -39,9 +39,9 @@ dependencies { libraries.autovalue_annotation, libraries.opencensus_proto, libraries.protobuf_util - implementation project(path: ':grpc-rls') def nettyDependency = implementation project(':grpc-netty') + testImplementation project(':grpc-rls') testImplementation project(':grpc-core').sourceSets.test.output annotationProcessor libraries.autovalue @@ -71,7 +71,7 @@ sourceSets { proto { srcDir 'third_party/envoy/src/main/proto' srcDir 'third_party/protoc-gen-validate/src/main/proto' - srcDir 'third_party/udpa/src/main/proto' + srcDir 'third_party/xds/src/main/proto' srcDir 'third_party/googleapis/src/main/proto' srcDir 'third_party/istio/src/main/proto' } @@ -92,6 +92,7 @@ jar { javadoc { // Exclusions here should generally also be relocated exclude 'com/github/udpa/**' + exclude 'com/github/xds/**' exclude 'com/google/security/**' exclude 'io/envoyproxy/**' // Need to clean up the class structure to reduce how much is exposed @@ -115,6 +116,7 @@ shadowJar { } // Relocated packages commonly need exclusions in jacocoTestReport and javadoc relocate 'com.github.udpa', "${prefixName}.shaded.com.github.udpa" + relocate 'com.github.xds', "${prefixName}.shaded.com.github.xds" relocate 'com.google.api.expr', "${prefixName}.shaded.com.google.api.expr" relocate 'com.google.security', "${prefixName}.shaded.com.google.security" // TODO: missing java_package option in .proto @@ -124,6 +126,7 @@ shadowJar { relocate 'io.netty', 'io.grpc.netty.shaded.io.netty' // TODO: missing java_package option in .proto relocate 'udpa.annotations', "${prefixName}.shaded.udpa.annotations" + relocate 'xds.annotations', "${prefixName}.shaded.xds.annotations" exclude "**/*.proto" } @@ -159,11 +162,13 @@ jacocoTestReport { fileTree(dir: it, exclude: [ // Exclusions here should generally also be relocated '**/com/github/udpa/**', + '**/com/github/xds/**', '**/com/google/api/expr/**', '**/com/google/security/**', '**/envoy/annotations/**', '**/io/envoyproxy/**', '**/udpa/annotations/**', + '**/xds/annotations/**', ]) } } diff --git a/xds/src/generated/main/grpc/com/github/udpa/udpa/service/orca/v1/OpenRcaServiceGrpc.java b/xds/src/generated/main/grpc/com/github/xds/service/orca/v3/OpenRcaServiceGrpc.java similarity index 83% rename from xds/src/generated/main/grpc/com/github/udpa/udpa/service/orca/v1/OpenRcaServiceGrpc.java rename to xds/src/generated/main/grpc/com/github/xds/service/orca/v3/OpenRcaServiceGrpc.java index 78383dba2d35..52ec68988085 100644 --- a/xds/src/generated/main/grpc/com/github/udpa/udpa/service/orca/v1/OpenRcaServiceGrpc.java +++ b/xds/src/generated/main/grpc/com/github/xds/service/orca/v3/OpenRcaServiceGrpc.java @@ -1,4 +1,4 @@ -package com.github.udpa.udpa.service.orca.v1; +package com.github.xds.service.orca.v3; import static io.grpc.MethodDescriptor.generateFullMethodName; @@ -16,38 +16,38 @@ */ @javax.annotation.Generated( value = "by gRPC proto compiler", - comments = "Source: udpa/service/orca/v1/orca.proto") + comments = "Source: xds/service/orca/v3/orca.proto") @io.grpc.stub.annotations.GrpcGenerated public final class OpenRcaServiceGrpc { private OpenRcaServiceGrpc() {} - public static final String SERVICE_NAME = "udpa.service.orca.v1.OpenRcaService"; + public static final String SERVICE_NAME = "xds.service.orca.v3.OpenRcaService"; // Static method descriptors that strictly reflect the proto. - private static volatile io.grpc.MethodDescriptor getStreamCoreMetricsMethod; + private static volatile io.grpc.MethodDescriptor getStreamCoreMetricsMethod; @io.grpc.stub.annotations.RpcMethod( fullMethodName = SERVICE_NAME + '/' + "StreamCoreMetrics", - requestType = com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest.class, - responseType = com.github.udpa.udpa.data.orca.v1.OrcaLoadReport.class, + requestType = com.github.xds.service.orca.v3.OrcaLoadReportRequest.class, + responseType = com.github.xds.data.orca.v3.OrcaLoadReport.class, methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) - public static io.grpc.MethodDescriptor getStreamCoreMetricsMethod() { - io.grpc.MethodDescriptor getStreamCoreMetricsMethod; + public static io.grpc.MethodDescriptor getStreamCoreMetricsMethod() { + io.grpc.MethodDescriptor getStreamCoreMetricsMethod; if ((getStreamCoreMetricsMethod = OpenRcaServiceGrpc.getStreamCoreMetricsMethod) == null) { synchronized (OpenRcaServiceGrpc.class) { if ((getStreamCoreMetricsMethod = OpenRcaServiceGrpc.getStreamCoreMetricsMethod) == null) { OpenRcaServiceGrpc.getStreamCoreMetricsMethod = getStreamCoreMetricsMethod = - io.grpc.MethodDescriptor.newBuilder() + io.grpc.MethodDescriptor.newBuilder() .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StreamCoreMetrics")) .setSampledToLocalTracing(true) .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest.getDefaultInstance())) + com.github.xds.service.orca.v3.OrcaLoadReportRequest.getDefaultInstance())) .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - com.github.udpa.udpa.data.orca.v1.OrcaLoadReport.getDefaultInstance())) + com.github.xds.data.orca.v3.OrcaLoadReport.getDefaultInstance())) .setSchemaDescriptor(new OpenRcaServiceMethodDescriptorSupplier("StreamCoreMetrics")) .build(); } @@ -116,8 +116,8 @@ public static abstract class OpenRcaServiceImplBase implements io.grpc.BindableS /** */ - public void streamCoreMetrics(com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest request, - io.grpc.stub.StreamObserver responseObserver) { + public void streamCoreMetrics(com.github.xds.service.orca.v3.OrcaLoadReportRequest request, + io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStreamCoreMetricsMethod(), responseObserver); } @@ -127,8 +127,8 @@ public void streamCoreMetrics(com.github.udpa.udpa.service.orca.v1.OrcaLoadRepor getStreamCoreMetricsMethod(), io.grpc.stub.ServerCalls.asyncServerStreamingCall( new MethodHandlers< - com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest, - com.github.udpa.udpa.data.orca.v1.OrcaLoadReport>( + com.github.xds.service.orca.v3.OrcaLoadReportRequest, + com.github.xds.data.orca.v3.OrcaLoadReport>( this, METHODID_STREAM_CORE_METRICS))) .build(); } @@ -160,8 +160,8 @@ protected OpenRcaServiceStub build( /** */ - public void streamCoreMetrics(com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest request, - io.grpc.stub.StreamObserver responseObserver) { + public void streamCoreMetrics(com.github.xds.service.orca.v3.OrcaLoadReportRequest request, + io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ClientCalls.asyncServerStreamingCall( getChannel().newCall(getStreamCoreMetricsMethod(), getCallOptions()), request, responseObserver); } @@ -193,8 +193,8 @@ protected OpenRcaServiceBlockingStub build( /** */ - public java.util.Iterator streamCoreMetrics( - com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest request) { + public java.util.Iterator streamCoreMetrics( + com.github.xds.service.orca.v3.OrcaLoadReportRequest request) { return io.grpc.stub.ClientCalls.blockingServerStreamingCall( getChannel(), getStreamCoreMetricsMethod(), getCallOptions(), request); } @@ -245,8 +245,8 @@ private static final class MethodHandlers implements public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { switch (methodId) { case METHODID_STREAM_CORE_METRICS: - serviceImpl.streamCoreMetrics((com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest) request, - (io.grpc.stub.StreamObserver) responseObserver); + serviceImpl.streamCoreMetrics((com.github.xds.service.orca.v3.OrcaLoadReportRequest) request, + (io.grpc.stub.StreamObserver) responseObserver); break; default: throw new AssertionError(); @@ -270,7 +270,7 @@ private static abstract class OpenRcaServiceBaseDescriptorSupplier @java.lang.Override public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { - return com.github.udpa.udpa.service.orca.v1.OrcaProto.getDescriptor(); + return com.github.xds.service.orca.v3.OrcaProto.getDescriptor(); } @java.lang.Override diff --git a/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java b/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java index 0e609ff74587..61780c60a554 100644 --- a/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java @@ -28,6 +28,7 @@ import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; +import io.grpc.Channel; import io.grpc.Context; import io.grpc.InternalLogId; import io.grpc.ManagedChannel; @@ -36,6 +37,11 @@ import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; import io.grpc.stub.StreamObserver; +import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.ClientXdsClient.XdsChannelFactory; +import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.XdsClient.ResourceStore; +import io.grpc.xds.XdsClient.XdsResponseHandler; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.Collection; import java.util.Collections; @@ -48,7 +54,7 @@ * Common base type for XdsClient implementations, which encapsulates the layer abstraction of * the xDS RPC stream. */ -abstract class AbstractXdsClient extends XdsClient { +final class AbstractXdsClient { private static final String ADS_TYPE_URL_LDS_V2 = "type.googleapis.com/envoy.api.v2.Listener"; private static final String ADS_TYPE_URL_LDS = @@ -66,26 +72,18 @@ abstract class AbstractXdsClient extends XdsClient { private static final String ADS_TYPE_URL_EDS = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; - private final SynchronizationContext syncContext = new SynchronizationContext( - new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - getLogger().log( - XdsLogLevel.ERROR, - "Uncaught exception in XdsClient SynchronizationContext. Panic!", - e); - // TODO(chengyuanzhang): better error handling. - throw new AssertionError(e); - } - }); + private final SynchronizationContext syncContext; private final InternalLogId logId; private final XdsLogger logger; + private final ServerInfo serverInfo; private final ManagedChannel channel; + private final XdsResponseHandler xdsResponseHandler; + private final ResourceStore resourceStore; private final Context context; private final ScheduledExecutorService timeService; private final BackoffPolicy.Provider backoffPolicyProvider; private final Stopwatch stopwatch; - private final Bootstrapper.BootstrapInfo bootstrapInfo; + private final Node bootstrapNode; // Last successfully applied version_info for each resource type. Starts with empty string. // A version_info is used to update management server with client's most recent knowledge of @@ -103,71 +101,42 @@ public void uncaughtException(Thread t, Throwable e) { @Nullable private ScheduledHandle rpcRetryTimer; - AbstractXdsClient(ManagedChannel channel, Bootstrapper.BootstrapInfo bootstrapInfo, - Context context, ScheduledExecutorService timeService, - BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { - this.channel = checkNotNull(channel, "channel"); - this.bootstrapInfo = checkNotNull(bootstrapInfo, "bootstrapInfo"); + /** An entity that manages ADS RPCs over a single channel. */ + // TODO: rename to XdsChannel + AbstractXdsClient( + XdsChannelFactory xdsChannelFactory, + ServerInfo serverInfo, + Node bootstrapNode, + XdsResponseHandler xdsResponseHandler, + ResourceStore resourceStore, + Context context, + ScheduledExecutorService + timeService, + SynchronizationContext syncContext, + BackoffPolicy.Provider backoffPolicyProvider, + Supplier stopwatchSupplier) { + this.serverInfo = checkNotNull(serverInfo, "serverInfo"); + this.channel = checkNotNull(xdsChannelFactory, "xdsChannelFactory").create(serverInfo); + this.xdsResponseHandler = checkNotNull(xdsResponseHandler, "xdsResponseHandler"); + this.resourceStore = checkNotNull(resourceStore, "resourcesSubscriber"); + this.bootstrapNode = checkNotNull(bootstrapNode, "bootstrapNode"); this.context = checkNotNull(context, "context"); this.timeService = checkNotNull(timeService, "timeService"); + this.syncContext = checkNotNull(syncContext, "syncContext"); this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); stopwatch = checkNotNull(stopwatchSupplier, "stopwatchSupplier").get(); - logId = InternalLogId.allocate("xds-client", null); + logId = InternalLogId.allocate("xds-client", serverInfo.target()); logger = XdsLogger.withLogId(logId); logger.log(XdsLogLevel.INFO, "Created"); } - /** - * Called when an LDS response is received. - */ - // Must be synchronized. - protected void handleLdsResponse(String versionInfo, List resources, String nonce) { - } - - /** - * Called when a RDS response is received. - */ - // Must be synchronized. - protected void handleRdsResponse(String versionInfo, List resources, String nonce) { - } - - /** - * Called when a CDS response is received. - */ - // Must be synchronized. - protected void handleCdsResponse(String versionInfo, List resources, String nonce) { - } - - /** - * Called when an EDS response is received. - */ - // Must be synchronized. - protected void handleEdsResponse(String versionInfo, List resources, String nonce) { - } - - /** - * Called when the ADS stream is closed passively. - */ - // Must be synchronized. - protected void handleStreamClosed(Status error) { - } - - /** - * Called when the ADS stream has been recreated. - */ - // Must be synchronized. - protected void handleStreamRestarted() { - } - - /** - * Called when being shut down. - */ - // Must be synchronized. - protected void handleShutdown() { + /** The underlying channel. */ + // Currently, only externally used for LrsClient. + Channel channel() { + return channel; } - @Override - final void shutdown() { + void shutdown() { syncContext.execute(new Runnable() { @Override public void run() { @@ -179,49 +148,28 @@ public void run() { if (rpcRetryTimer != null && rpcRetryTimer.isPending()) { rpcRetryTimer.cancel(); } - handleShutdown(); + channel.shutdown(); } }); } - @Override - boolean isShutDown() { - return shutdown; - } - - @Override - Bootstrapper.BootstrapInfo getBootstrapInfo() { - return bootstrapInfo; - } - @Override public String toString() { return logId.toString(); } - /** - * Returns the collection of resources currently subscribing to or {@code null} if not - * subscribing to any resources for the given type. - * - *

Note an empty collection indicates subscribing to resources of the given type with - * wildcard mode. - */ - // Must be synchronized. - @Nullable - abstract Collection getSubscribedResources(ResourceType type); - /** * Updates the resource subscription for the given resource type. */ // Must be synchronized. - protected final void adjustResourceSubscription(ResourceType type) { + void adjustResourceSubscription(ResourceType type) { if (isInBackoff()) { return; } if (adsStream == null) { startRpcStream(); } - Collection resources = getSubscribedResources(type); + Collection resources = resourceStore.getSubscribedResources(serverInfo, type); if (resources != null) { adsStream.sendDiscoveryRequest(type, resources); } @@ -232,7 +180,7 @@ protected final void adjustResourceSubscription(ResourceType type) { * and sends an ACK request to the management server. */ // Must be synchronized. - protected final void ackResponse(ResourceType type, String versionInfo, String nonce) { + void ackResponse(ResourceType type, String versionInfo, String nonce) { switch (type) { case LDS: ldsVersion = versionInfo; @@ -252,7 +200,7 @@ protected final void ackResponse(ResourceType type, String versionInfo, String n } logger.log(XdsLogLevel.INFO, "Sending ACK for {0} update, nonce: {1}, current version: {2}", type, nonce, versionInfo); - Collection resources = getSubscribedResources(type); + Collection resources = resourceStore.getSubscribedResources(serverInfo, type); if (resources == null) { resources = Collections.emptyList(); } @@ -264,34 +212,22 @@ protected final void ackResponse(ResourceType type, String versionInfo, String n * accepted version) to the management server. */ // Must be synchronized. - protected final void nackResponse(ResourceType type, String nonce, String errorDetail) { + void nackResponse(ResourceType type, String nonce, String errorDetail) { String versionInfo = getCurrentVersion(type); logger.log(XdsLogLevel.INFO, "Sending NACK for {0} update, nonce: {1}, current version: {2}", type, nonce, versionInfo); - Collection resources = getSubscribedResources(type); + Collection resources = resourceStore.getSubscribedResources(serverInfo, type); if (resources == null) { resources = Collections.emptyList(); } adsStream.sendDiscoveryRequest(type, versionInfo, resources, nonce, errorDetail); } - protected final SynchronizationContext getSyncContext() { - return syncContext; - } - - protected final ScheduledExecutorService getTimeService() { - return timeService; - } - - protected final XdsLogger getLogger() { - return logger; - } - /** * Returns {@code true} if the resource discovery is currently in backoff. */ // Must be synchronized. - protected final boolean isInBackoff() { + boolean isInBackoff() { return rpcRetryTimer != null && rpcRetryTimer.isPending(); } @@ -302,7 +238,7 @@ protected final boolean isInBackoff() { // Must be synchronized. private void startRpcStream() { checkState(adsStream == null, "Previous adsStream has not been cleared yet"); - if (bootstrapInfo.servers().get(0).useProtocolV3()) { + if (serverInfo.useProtocolV3()) { adsStream = new AdsStreamV3(); } else { adsStream = new AdsStreamV2(); @@ -317,8 +253,8 @@ private void startRpcStream() { stopwatch.reset().start(); } + /** Returns the latest accepted version of the given resource type. */ // Must be synchronized. - @Override String getCurrentVersion(ResourceType type) { String version; switch (type) { @@ -353,16 +289,16 @@ public void run() { if (type == ResourceType.UNKNOWN) { continue; } - Collection resources = getSubscribedResources(type); + Collection resources = resourceStore.getSubscribedResources(serverInfo, type); if (resources != null) { adsStream.sendDiscoveryRequest(type, resources); } } - handleStreamRestarted(); + xdsResponseHandler.handleStreamRestarted(serverInfo); } } - protected enum ResourceType { + enum ResourceType { UNKNOWN, LDS, RDS, CDS, EDS; String typeUrl() { @@ -397,7 +333,8 @@ String typeUrlV2() { } } - private static ResourceType fromTypeUrl(String typeUrl) { + @VisibleForTesting + static ResourceType fromTypeUrl(String typeUrl) { switch (typeUrl) { case ADS_TYPE_URL_LDS: // fall trough @@ -488,19 +425,19 @@ final void handleRpcResponse( switch (type) { case LDS: ldsRespNonce = nonce; - handleLdsResponse(versionInfo, resources, nonce); + xdsResponseHandler.handleLdsResponse(serverInfo, versionInfo, resources, nonce); break; case RDS: rdsRespNonce = nonce; - handleRdsResponse(versionInfo, resources, nonce); + xdsResponseHandler.handleRdsResponse(serverInfo, versionInfo, resources, nonce); break; case CDS: cdsRespNonce = nonce; - handleCdsResponse(versionInfo, resources, nonce); + xdsResponseHandler.handleCdsResponse(serverInfo, versionInfo, resources, nonce); break; case EDS: edsRespNonce = nonce; - handleEdsResponse(versionInfo, resources, nonce); + xdsResponseHandler.handleEdsResponse(serverInfo, versionInfo, resources, nonce); break; case UNKNOWN: default: @@ -526,7 +463,7 @@ private void handleRpcStreamClosed(Status error) { "ADS stream closed with status {0}: {1}. Cause: {2}", error.getCode(), error.getDescription(), error.getCause()); closed = true; - handleStreamClosed(error); + xdsResponseHandler.handleStreamClosed(error); cleanUp(); if (responseReceived || retryBackoffPolicy == null) { // Reset the backoff sequence if had received a response, or backoff sequence @@ -619,7 +556,7 @@ void sendDiscoveryRequest(ResourceType type, String versionInfo, CollectionA filesystem path defined by environment variable "GRPC_XDS_BOOTSTRAP" *

  • A filesystem path defined by Java System Property "io.grpc.xds.bootstrap"
  • *
  • Environment variable value of "GRPC_XDS_BOOTSTRAP_CONFIG"
  • - *
  • Java System Property value of "io.grpc.xds.bootstrap_value"
  • + *
  • Java System Property value of "io.grpc.xds.bootstrapConfig"
  • * */ @SuppressWarnings("unchecked") diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index 4af187bf1dd4..396292a23927 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -157,12 +157,12 @@ private void handleClusterDiscovered() { if (clusterState.result.clusterType() == ClusterType.EDS) { instance = DiscoveryMechanism.forEds( clusterState.name, clusterState.result.edsServiceName(), - clusterState.result.lrsServerName(), clusterState.result.maxConcurrentRequests(), + clusterState.result.lrsServerInfo(), clusterState.result.maxConcurrentRequests(), clusterState.result.upstreamTlsContext()); } else { // logical DNS instance = DiscoveryMechanism.forLogicalDns( clusterState.name, clusterState.result.dnsHostName(), - clusterState.result.lrsServerName(), clusterState.result.maxConcurrentRequests(), + clusterState.result.lrsServerInfo(), clusterState.result.maxConcurrentRequests(), clusterState.result.upstreamTlsContext()); } instances.add(instance); diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 2a4405f45fca..0a11ad472880 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -27,6 +27,7 @@ import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.protobuf.Any; import com.google.protobuf.Duration; import com.google.protobuf.InvalidProtocolBufferException; @@ -56,14 +57,20 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; +import io.grpc.ChannelCredentials; import io.grpc.Context; import io.grpc.EquivalentAddressGroup; +import io.grpc.Grpc; +import io.grpc.InternalLogId; import io.grpc.ManagedChannel; import io.grpc.Status; import io.grpc.Status.Code; +import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; import io.grpc.internal.TimeProvider; +import io.grpc.xds.AbstractXdsClient.ResourceType; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints; @@ -85,6 +92,8 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; +import io.grpc.xds.XdsClient.ResourceStore; +import io.grpc.xds.XdsClient.XdsResponseHandler; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.internal.Matchers.FractionMatcher; import io.grpc.xds.internal.Matchers.HeaderMatcher; @@ -103,7 +112,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -111,7 +119,7 @@ /** * XdsClient implementation for client side usages. */ -final class ClientXdsClient extends AbstractXdsClient { +final class ClientXdsClient extends XdsClient implements XdsResponseHandler, ResourceStore { // Longest time to wait, since the subscription to some resource, for concluding its absence. @VisibleForTesting @@ -154,8 +162,10 @@ final class ClientXdsClient extends AbstractXdsClient { "type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig"; private static final String TYPE_URL_CLUSTER_CONFIG = "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig"; - private static final String TYPE_URL_TYPED_STRUCT = + private static final String TYPE_URL_TYPED_STRUCT_UDPA = "type.googleapis.com/udpa.type.v1.TypedStruct"; + private static final String TYPE_URL_TYPED_STRUCT = + "type.googleapis.com/xds.type.v3.TypedStruct"; private static final String TYPE_URL_FILTER_CONFIG = "type.googleapis.com/envoy.config.route.v3.FilterConfig"; // TODO(zdapeng): need to discuss how to handle unsupported values. @@ -164,33 +174,90 @@ final class ClientXdsClient extends AbstractXdsClient { Code.CANCELLED, Code.DEADLINE_EXCEEDED, Code.INTERNAL, Code.RESOURCE_EXHAUSTED, Code.UNAVAILABLE)); + private final SynchronizationContext syncContext = new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.log( + XdsLogLevel.ERROR, + "Uncaught exception in XdsClient SynchronizationContext. Panic!", + e); + // TODO(chengyuanzhang): better error handling. + throw new AssertionError(e); + } + }); private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry(); + private final Map serverChannelMap = new HashMap<>(); private final Map ldsResourceSubscribers = new HashMap<>(); private final Map rdsResourceSubscribers = new HashMap<>(); private final Map cdsResourceSubscribers = new HashMap<>(); private final Map edsResourceSubscribers = new HashMap<>(); private final LoadStatsManager2 loadStatsManager; - private final LoadReportClient lrsClient; + private final Map serverLrsClientMap = new HashMap<>(); + private final XdsChannelFactory xdsChannelFactory; + private final Bootstrapper.BootstrapInfo bootstrapInfo; + private final Context context; + private final ScheduledExecutorService timeService; + private final BackoffPolicy.Provider backoffPolicyProvider; + private final Supplier stopwatchSupplier; private final TimeProvider timeProvider; private boolean reportingLoad; private final TlsContextManager tlsContextManager; + private final InternalLogId logId; + private final XdsLogger logger; + private volatile boolean isShutdown; + // TODO(zdapeng): rename to XdsClientImpl ClientXdsClient( - ManagedChannel channel, Bootstrapper.BootstrapInfo bootstrapInfo, Context context, - ScheduledExecutorService timeService, BackoffPolicy.Provider backoffPolicyProvider, - Supplier stopwatchSupplier, TimeProvider timeProvider, + XdsChannelFactory xdsChannelFactory, + Bootstrapper.BootstrapInfo bootstrapInfo, + Context context, + ScheduledExecutorService timeService, + BackoffPolicy.Provider backoffPolicyProvider, + Supplier stopwatchSupplier, + TimeProvider timeProvider, TlsContextManager tlsContextManager) { - super(channel, bootstrapInfo, context, timeService, backoffPolicyProvider, stopwatchSupplier); + this.xdsChannelFactory = xdsChannelFactory; + this.bootstrapInfo = bootstrapInfo; + this.context = context; + this.timeService = timeService; loadStatsManager = new LoadStatsManager2(stopwatchSupplier); + this.backoffPolicyProvider = backoffPolicyProvider; + this.stopwatchSupplier = stopwatchSupplier; this.timeProvider = timeProvider; this.tlsContextManager = checkNotNull(tlsContextManager, "tlsContextManager"); - lrsClient = new LoadReportClient(loadStatsManager, channel, context, - bootstrapInfo.servers().get(0).useProtocolV3(), bootstrapInfo.node(), - getSyncContext(), timeService, backoffPolicyProvider, stopwatchSupplier); + logId = InternalLogId.allocate("xds-client", null); + logger = XdsLogger.withLogId(logId); + logger.log(XdsLogLevel.INFO, "Created"); + } + + private void maybeCreateXdsChannelWithLrs(ServerInfo serverInfo) { + syncContext.throwIfNotInThisSynchronizationContext(); + if (serverChannelMap.containsKey(serverInfo)) { + return; + } + AbstractXdsClient xdsChannel = new AbstractXdsClient( + xdsChannelFactory, + serverInfo, + bootstrapInfo.node(), + this, + this, + context, + timeService, + syncContext, + backoffPolicyProvider, + stopwatchSupplier); + LoadReportClient lrsClient = new LoadReportClient( + loadStatsManager, xdsChannel.channel(), context, serverInfo.useProtocolV3(), + bootstrapInfo.node(), syncContext, timeService, backoffPolicyProvider, stopwatchSupplier); + serverChannelMap.put(serverInfo, xdsChannel); + serverLrsClientMap.put(serverInfo, lrsClient); } @Override - protected void handleLdsResponse(String versionInfo, List resources, String nonce) { + public void handleLdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce) { + syncContext.throwIfNotInThisSynchronizationContext(); Map parsedResources = new HashMap<>(resources.size()); Set unpackedResources = new HashSet<>(resources.size()); Set invalidResources = new HashSet<>(); @@ -233,12 +300,12 @@ protected void handleLdsResponse(String versionInfo, List resources, String // LdsUpdate parsed successfully. parsedResources.put(listenerName, new ParsedResource(ldsUpdate, resource)); } - getLogger().log(XdsLogLevel.INFO, + logger.log(XdsLogLevel.INFO, "Received LDS Response version {0} nonce {1}. Parsed resources: {2}", versionInfo, nonce, unpackedResources); handleResourceUpdate( - ResourceType.LDS, parsedResources, invalidResources, retainedRdsResources, versionInfo, - nonce, errors); + serverInfo, ResourceType.LDS, parsedResources, invalidResources, retainedRdsResources, + versionInfo, nonce, errors); } private LdsUpdate processClientSideListener( @@ -375,14 +442,10 @@ static FilterChain parseFilterChain( validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances)); } - String name = proto.getName(); - if (name.isEmpty()) { - name = UUID.randomUUID().toString(); - } FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch()); checkForUniqueness(uniqueSet, filterChainMatch); return new FilterChain( - name, + proto.getName(), filterChainMatch, httpConnectionManager, downstreamTlsContext, @@ -847,16 +910,21 @@ static StructOrError parseHttpFilter( } Message rawConfig = httpFilter.getTypedConfig(); String typeUrl = httpFilter.getTypedConfig().getTypeUrl(); - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - TypedStruct typedStruct; - try { - typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class); - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "HttpFilter [" + filterName + "] contains invalid proto: " + e); + + try { + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { + TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + com.github.xds.type.v3.TypedStruct newTypedStruct = + httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class); + typeUrl = newTypedStruct.getTypeUrl(); + rawConfig = newTypedStruct.getValue(); } - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "HttpFilter [" + filterName + "] contains invalid proto: " + e); } Filter filter = filterRegistry.get(typeUrl); if ((isForClient && !(filter instanceof ClientInterceptorBuilder)) @@ -930,16 +998,20 @@ static StructOrError> parseOverrideFilterConfigs( typeUrl = anyConfig.getTypeUrl(); } Message rawConfig = anyConfig; - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - TypedStruct typedStruct; - try { - typedStruct = anyConfig.unpack(TypedStruct.class); - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "FilterConfig [" + name + "] contains invalid proto: " + e); + try { + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { + TypedStruct typedStruct = anyConfig.unpack(TypedStruct.class); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + com.github.xds.type.v3.TypedStruct newTypedStruct = + anyConfig.unpack(com.github.xds.type.v3.TypedStruct.class); + typeUrl = newTypedStruct.getTypeUrl(); + rawConfig = newTypedStruct.getValue(); } - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "FilterConfig [" + name + "] contains invalid proto: " + e); } Filter filter = filterRegistry.get(typeUrl); if (filter == null) { @@ -1307,7 +1379,9 @@ static StructOrError parseClusterWeight( } @Override - protected void handleRdsResponse(String versionInfo, List resources, String nonce) { + public void handleRdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce) { + syncContext.throwIfNotInThisSynchronizationContext(); Map parsedResources = new HashMap<>(resources.size()); Set unpackedResources = new HashSet<>(resources.size()); Set invalidResources = new HashSet<>(); @@ -1344,12 +1418,12 @@ protected void handleRdsResponse(String versionInfo, List resources, String parsedResources.put(routeConfigName, new ParsedResource(rdsUpdate, resource)); } - getLogger().log(XdsLogLevel.INFO, + logger.log(XdsLogLevel.INFO, "Received RDS Response version {0} nonce {1}. Parsed resources: {2}", versionInfo, nonce, unpackedResources); handleResourceUpdate( - ResourceType.RDS, parsedResources, invalidResources, Collections.emptySet(), - versionInfo, nonce, errors); + serverInfo, ResourceType.RDS, parsedResources, invalidResources, + Collections.emptySet(), versionInfo, nonce, errors); } private static RdsUpdate processRouteConfiguration( @@ -1370,7 +1444,9 @@ private static RdsUpdate processRouteConfiguration( } @Override - protected void handleCdsResponse(String versionInfo, List resources, String nonce) { + public void handleCdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce) { + syncContext.throwIfNotInThisSynchronizationContext(); Map parsedResources = new HashMap<>(resources.size()); Set unpackedResources = new HashSet<>(resources.size()); Set invalidResources = new HashSet<>(); @@ -1406,7 +1482,7 @@ protected void handleCdsResponse(String versionInfo, List resources, String if (getBootstrapInfo() != null && getBootstrapInfo().certProviders() != null) { certProviderInstances = getBootstrapInfo().certProviders().keySet(); } - cdsUpdate = parseCluster(cluster, retainedEdsResources, certProviderInstances); + cdsUpdate = parseCluster(cluster, retainedEdsResources, certProviderInstances, serverInfo); } catch (ResourceInvalidException e) { errors.add( "CDS response Cluster '" + clusterName + "' validation error: " + e.getMessage()); @@ -1415,23 +1491,23 @@ protected void handleCdsResponse(String versionInfo, List resources, String } parsedResources.put(clusterName, new ParsedResource(cdsUpdate, resource)); } - getLogger().log(XdsLogLevel.INFO, + logger.log(XdsLogLevel.INFO, "Received CDS Response version {0} nonce {1}. Parsed resources: {2}", versionInfo, nonce, unpackedResources); handleResourceUpdate( - ResourceType.CDS, parsedResources, invalidResources, retainedEdsResources, versionInfo, - nonce, errors); + serverInfo, ResourceType.CDS, parsedResources, invalidResources, retainedEdsResources, + versionInfo, nonce, errors); } @VisibleForTesting static CdsUpdate parseCluster(Cluster cluster, Set retainedEdsResources, - Set certProviderInstances) + Set certProviderInstances, ServerInfo serverInfo) throws ResourceInvalidException { StructOrError structOrError; switch (cluster.getClusterDiscoveryTypeCase()) { case TYPE: structOrError = parseNonAggregateCluster(cluster, retainedEdsResources, - certProviderInstances); + certProviderInstances, serverInfo); break; case CLUSTER_TYPE: structOrError = parseAggregateCluster(cluster); @@ -1494,9 +1570,10 @@ private static StructOrError parseAggregateCluster(Cluster cl } private static StructOrError parseNonAggregateCluster( - Cluster cluster, Set edsResources, Set certProviderInstances) { + Cluster cluster, Set edsResources, Set certProviderInstances, + ServerInfo serverInfo) { String clusterName = cluster.getName(); - String lrsServerName = null; + ServerInfo lrsServerInfo = null; Long maxConcurrentRequests = null; UpstreamTlsContext upstreamTlsContext = null; if (cluster.hasLrsServer()) { @@ -1504,7 +1581,7 @@ private static StructOrError parseNonAggregateCluster( return StructOrError.fromError( "Cluster " + clusterName + ": only support LRS for the same management server"); } - lrsServerName = ""; + lrsServerInfo = serverInfo; } if (cluster.hasCircuitBreakers()) { List thresholds = cluster.getCircuitBreakers().getThresholdsList(); @@ -1556,7 +1633,7 @@ private static StructOrError parseNonAggregateCluster( edsResources.add(clusterName); } return StructOrError.fromStruct(CdsUpdate.forEds( - clusterName, edsServiceName, lrsServerName, maxConcurrentRequests, upstreamTlsContext)); + clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); } else if (type.equals(DiscoveryType.LOGICAL_DNS)) { if (!cluster.hasLoadAssignment()) { return StructOrError.fromError( @@ -1591,14 +1668,16 @@ private static StructOrError parseNonAggregateCluster( String dnsHostName = String.format("%s:%d", socketAddress.getAddress(), socketAddress.getPortValue()); return StructOrError.fromStruct(CdsUpdate.forLogicalDns( - clusterName, dnsHostName, lrsServerName, maxConcurrentRequests, upstreamTlsContext)); + clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); } return StructOrError.fromError( "Cluster " + clusterName + ": unsupported built-in discovery type: " + type); } @Override - protected void handleEdsResponse(String versionInfo, List resources, String nonce) { + public void handleEdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce) { + syncContext.throwIfNotInThisSynchronizationContext(); Map parsedResources = new HashMap<>(resources.size()); Set unpackedResources = new HashSet<>(resources.size()); Set invalidResources = new HashSet<>(); @@ -1641,12 +1720,12 @@ protected void handleEdsResponse(String versionInfo, List resources, String } parsedResources.put(clusterName, new ParsedResource(edsUpdate, resource)); } - getLogger().log( + logger.log( XdsLogLevel.INFO, "Received EDS Response version {0} nonce {1}. Parsed resources: {2}", versionInfo, nonce, unpackedResources); handleResourceUpdate( - ResourceType.EDS, parsedResources, invalidResources, Collections.emptySet(), - versionInfo, nonce, errors); + serverInfo, ResourceType.EDS, parsedResources, invalidResources, + Collections.emptySet(), versionInfo, nonce, errors); } private static EdsUpdate processClusterLoadAssignment(ClusterLoadAssignment assignment) @@ -1775,7 +1854,8 @@ private static int getRatePerMillion(FractionalPercent percent) { } @Override - protected void handleStreamClosed(Status error) { + public void handleStreamClosed(Status error) { + syncContext.throwIfNotInThisSynchronizationContext(); cleanUpResourceTimers(); for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { subscriber.onError(error); @@ -1792,27 +1872,56 @@ protected void handleStreamClosed(Status error) { } @Override - protected void handleStreamRestarted() { + public void handleStreamRestarted(ServerInfo serverInfo) { + syncContext.throwIfNotInThisSynchronizationContext(); for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - subscriber.restartTimer(); + if (subscriber.serverInfo.equals(serverInfo)) { + subscriber.restartTimer(); + } } for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - subscriber.restartTimer(); + if (subscriber.serverInfo.equals(serverInfo)) { + subscriber.restartTimer(); + } } for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - subscriber.restartTimer(); + if (subscriber.serverInfo.equals(serverInfo)) { + subscriber.restartTimer(); + } } for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - subscriber.restartTimer(); + if (subscriber.serverInfo.equals(serverInfo)) { + subscriber.restartTimer(); + } } } @Override - protected void handleShutdown() { - if (reportingLoad) { - lrsClient.stopLoadReporting(); - } - cleanUpResourceTimers(); + void shutdown() { + syncContext.execute( + new Runnable() { + @Override + public void run() { + if (isShutdown) { + return; + } + isShutdown = true; + for (AbstractXdsClient xdsChannel : serverChannelMap.values()) { + xdsChannel.shutdown(); + } + if (reportingLoad) { + for (final LoadReportClient lrsClient : serverLrsClientMap.values()) { + lrsClient.stopLoadReporting(); + } + } + cleanUpResourceTimers(); + } + }); + } + + @Override + boolean isShutDown() { + return isShutdown; } private Map getSubscribedResourcesMap(ResourceType type) { @@ -1833,9 +1942,16 @@ private Map getSubscribedResourcesMap(ResourceType t @Nullable @Override - Collection getSubscribedResources(ResourceType type) { + public Collection getSubscribedResources(ServerInfo serverInfo, ResourceType type) { Map resources = getSubscribedResourcesMap(type); - return resources.isEmpty() ? null : resources.keySet(); + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (String key : resources.keySet()) { + if (resources.get(key).serverInfo.equals(serverInfo)) { + builder.add(key); + } + } + Collection retVal = builder.build(); + return retVal.isEmpty() ? null : retVal; } @Override @@ -1854,15 +1970,15 @@ TlsContextManager getTlsContextManager() { @Override void watchLdsResource(final String resourceName, final LdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = ldsResourceSubscribers.get(resourceName); if (subscriber == null) { - getLogger().log(XdsLogLevel.INFO, "Subscribe LDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Subscribe LDS resource {0}", resourceName); subscriber = new ResourceSubscriber(ResourceType.LDS, resourceName); ldsResourceSubscribers.put(resourceName, subscriber); - adjustResourceSubscription(ResourceType.LDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.LDS); } subscriber.addWatcher(watcher); } @@ -1871,16 +1987,16 @@ public void run() { @Override void cancelLdsResourceWatch(final String resourceName, final LdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = ldsResourceSubscribers.get(resourceName); subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.stopTimer(); - getLogger().log(XdsLogLevel.INFO, "Unsubscribe LDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Unsubscribe LDS resource {0}", resourceName); ldsResourceSubscribers.remove(resourceName); - adjustResourceSubscription(ResourceType.LDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.LDS); } } }); @@ -1888,15 +2004,15 @@ public void run() { @Override void watchRdsResource(final String resourceName, final RdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = rdsResourceSubscribers.get(resourceName); if (subscriber == null) { - getLogger().log(XdsLogLevel.INFO, "Subscribe RDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Subscribe RDS resource {0}", resourceName); subscriber = new ResourceSubscriber(ResourceType.RDS, resourceName); rdsResourceSubscribers.put(resourceName, subscriber); - adjustResourceSubscription(ResourceType.RDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.RDS); } subscriber.addWatcher(watcher); } @@ -1905,16 +2021,16 @@ public void run() { @Override void cancelRdsResourceWatch(final String resourceName, final RdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = rdsResourceSubscribers.get(resourceName); subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.stopTimer(); - getLogger().log(XdsLogLevel.INFO, "Unsubscribe RDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Unsubscribe RDS resource {0}", resourceName); rdsResourceSubscribers.remove(resourceName); - adjustResourceSubscription(ResourceType.RDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.RDS); } } }); @@ -1922,15 +2038,15 @@ public void run() { @Override void watchCdsResource(final String resourceName, final CdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = cdsResourceSubscribers.get(resourceName); if (subscriber == null) { - getLogger().log(XdsLogLevel.INFO, "Subscribe CDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Subscribe CDS resource {0}", resourceName); subscriber = new ResourceSubscriber(ResourceType.CDS, resourceName); cdsResourceSubscribers.put(resourceName, subscriber); - adjustResourceSubscription(ResourceType.CDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.CDS); } subscriber.addWatcher(watcher); } @@ -1939,16 +2055,16 @@ public void run() { @Override void cancelCdsResourceWatch(final String resourceName, final CdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = cdsResourceSubscribers.get(resourceName); subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.stopTimer(); - getLogger().log(XdsLogLevel.INFO, "Unsubscribe CDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Unsubscribe CDS resource {0}", resourceName); cdsResourceSubscribers.remove(resourceName); - adjustResourceSubscription(ResourceType.CDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.CDS); } } }); @@ -1956,15 +2072,15 @@ public void run() { @Override void watchEdsResource(final String resourceName, final EdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = edsResourceSubscribers.get(resourceName); if (subscriber == null) { - getLogger().log(XdsLogLevel.INFO, "Subscribe EDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Subscribe EDS resource {0}", resourceName); subscriber = new ResourceSubscriber(ResourceType.EDS, resourceName); edsResourceSubscribers.put(resourceName, subscriber); - adjustResourceSubscription(ResourceType.EDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.EDS); } subscriber.addWatcher(watcher); } @@ -1973,30 +2089,31 @@ public void run() { @Override void cancelEdsResourceWatch(final String resourceName, final EdsResourceWatcher watcher) { - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { ResourceSubscriber subscriber = edsResourceSubscribers.get(resourceName); subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.stopTimer(); - getLogger().log(XdsLogLevel.INFO, "Unsubscribe EDS resource {0}", resourceName); + logger.log(XdsLogLevel.INFO, "Unsubscribe EDS resource {0}", resourceName); edsResourceSubscribers.remove(resourceName); - adjustResourceSubscription(ResourceType.EDS); + subscriber.xdsChannel.adjustResourceSubscription(ResourceType.EDS); } } }); } @Override - ClusterDropStats addClusterDropStats(String clusterName, @Nullable String edsServiceName) { + ClusterDropStats addClusterDropStats( + final ServerInfo serverInfo, String clusterName, @Nullable String edsServiceName) { ClusterDropStats dropCounter = loadStatsManager.getClusterDropStats(clusterName, edsServiceName); - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { if (!reportingLoad) { - lrsClient.startLoadReporting(); + serverLrsClientMap.get(serverInfo).startLoadReporting(); reportingLoad = true; } } @@ -2005,15 +2122,16 @@ public void run() { } @Override - ClusterLocalityStats addClusterLocalityStats(String clusterName, - @Nullable String edsServiceName, Locality locality) { + ClusterLocalityStats addClusterLocalityStats( + final ServerInfo serverInfo, String clusterName, @Nullable String edsServiceName, + Locality locality) { ClusterLocalityStats loadCounter = loadStatsManager.getClusterLocalityStats(clusterName, edsServiceName, locality); - getSyncContext().execute(new Runnable() { + syncContext.execute(new Runnable() { @Override public void run() { if (!reportingLoad) { - lrsClient.startLoadReporting(); + serverLrsClientMap.get(serverInfo).startLoadReporting(); reportingLoad = true; } } @@ -2021,6 +2139,16 @@ public void run() { return loadCounter; } + @Override + Bootstrapper.BootstrapInfo getBootstrapInfo() { + return bootstrapInfo; + } + + @Override + public String toString() { + return logId.toString(); + } + private void cleanUpResourceTimers() { for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { subscriber.stopTimer(); @@ -2037,18 +2165,19 @@ private void cleanUpResourceTimers() { } private void handleResourceUpdate( - ResourceType type, Map parsedResources, Set invalidResources, - Set retainedResources, String version, String nonce, List errors) { + ServerInfo serverInfo, ResourceType type, Map parsedResources, + Set invalidResources, Set retainedResources, String version, String nonce, + List errors) { String errorDetail = null; if (errors.isEmpty()) { checkArgument(invalidResources.isEmpty(), "found invalid resources but missing errors"); - ackResponse(type, version, nonce); + serverChannelMap.get(serverInfo).ackResponse(type, version, nonce); } else { errorDetail = Joiner.on('\n').join(errors); - getLogger().log(XdsLogLevel.WARNING, + logger.log(XdsLogLevel.WARNING, "Failed processing {0} Response version {1} nonce {2}. Errors:\n{3}", type, version, nonce, errorDetail); - nackResponse(type, nonce, errorDetail); + serverChannelMap.get(serverInfo).nackResponse(type, nonce, errorDetail); } long updateTime = timeProvider.currentTimeNanos(); for (Map.Entry entry : getSubscribedResourcesMap(type).entrySet()) { @@ -2081,11 +2210,13 @@ private void handleResourceUpdate( } retainedResources.add(edsName); } - continue; + } else if (invalidResources.contains(resourceName)) { + subscriber.onError(Status.UNAVAILABLE.withDescription(errorDetail)); + } else { + // For State of the World services, notify watchers when their watched resource is missing + // from the ADS update. + subscriber.onAbsent(); } - // For State of the World services, notify watchers when their watched resource is missing - // from the ADS update. - subscriber.onAbsent(); } } // LDS/CDS responses represents the state of the world, RDS/EDS resources not referenced in @@ -2123,6 +2254,8 @@ private Any getRawResource() { * Tracks a single subscribed resource. */ private final class ResourceSubscriber { + private final ServerInfo serverInfo; + private final AbstractXdsClient xdsChannel; private final ResourceType type; private final String resource; private final Set watchers = new HashSet<>(); @@ -2132,17 +2265,26 @@ private final class ResourceSubscriber { private ResourceMetadata metadata; ResourceSubscriber(ResourceType type, String resource) { + syncContext.throwIfNotInThisSynchronizationContext(); this.type = type; this.resource = resource; + this.serverInfo = getServerInfo(); // Initialize metadata in UNKNOWN state to cover the case when resource subscriber, // is created but not yet requested because the client is in backoff. this.metadata = ResourceMetadata.newResourceMetadataUnknown(); - if (isInBackoff()) { + maybeCreateXdsChannelWithLrs(serverInfo); + this.xdsChannel = serverChannelMap.get(serverInfo); + if (xdsChannel.isInBackoff()) { return; } restartTimer(); } + // TODO(zdapeng): add resourceName arg and support xdstp:// resources + private ServerInfo getServerInfo() { + return bootstrapInfo.servers().get(0); // use first server + } + void addWatcher(ResourceWatcher watcher) { checkArgument(!watchers.contains(watcher), "watcher %s already registered", watcher); watchers.add(watcher); @@ -2165,7 +2307,7 @@ void restartTimer() { class ResourceNotFound implements Runnable { @Override public void run() { - getLogger().log(XdsLogLevel.INFO, "{0} resource {1} initial fetch timeout", + logger.log(XdsLogLevel.INFO, "{0} resource {1} initial fetch timeout", type, resource); respTimer = null; onAbsent(); @@ -2179,9 +2321,9 @@ public String toString() { // Initial fetch scheduled or rescheduled, transition metadata state to REQUESTED. metadata = ResourceMetadata.newResourceMetadataRequested(); - respTimer = getSyncContext().schedule( + respTimer = syncContext.schedule( new ResourceNotFound(), INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS, - getTimeService()); + timeService); } void stopTimer() { @@ -2216,7 +2358,7 @@ void onAbsent() { if (respTimer != null && respTimer.isPending()) { // too early to conclude absence return; } - getLogger().log(XdsLogLevel.INFO, "Conclude {0} resource {1} not exist", type, resource); + logger.log(XdsLogLevel.INFO, "Conclude {0} resource {1} not exist", type, resource); if (!absent) { data = null; absent = true; @@ -2324,4 +2466,19 @@ String getErrorDetail() { return errorDetail; } } + + abstract static class XdsChannelFactory { + static final XdsChannelFactory DEFAULT_XDS_CHANNEL_FACTORY = new XdsChannelFactory() { + @Override + ManagedChannel create(ServerInfo serverInfo) { + String target = serverInfo.target(); + ChannelCredentials channelCredentials = serverInfo.channelCredentials(); + return Grpc.newChannelBuilder(target, channelCredentials) + .keepAliveTime(5, TimeUnit.MINUTES) + .build(); + } + }; + + abstract ManagedChannel create(ServerInfo serverInfo); + } } diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 330c4e2f7a5e..60e6c696dafe 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -35,6 +35,7 @@ import io.grpc.internal.ObjectPool; import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.ForwardingSubchannel; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; @@ -117,17 +118,12 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { cluster = config.cluster; edsServiceName = config.edsServiceName; childLbHelper = new ClusterImplLbHelper( - callCounterProvider.getOrCreate(config.cluster, config.edsServiceName)); + callCounterProvider.getOrCreate(config.cluster, config.edsServiceName), + config.lrsServerInfo); childLb = config.childPolicy.getProvider().newLoadBalancer(childLbHelper); // Assume load report server does not change throughout cluster lifetime. - if (config.lrsServerName != null) { - if (config.lrsServerName.isEmpty()) { - dropStats = xdsClient.addClusterDropStats(cluster, edsServiceName); - } else { - logger.log(XdsLogLevel.WARNING, "Cluster {0} config error: can only report load " - + "to the same management server. Config lrsServerName {1} should be empty. ", - cluster, config.lrsServerName); - } + if (config.lrsServerInfo != null) { + dropStats = xdsClient.addClusterDropStats(config.lrsServerInfo, cluster, edsServiceName); } } childLbHelper.updateDropPolicies(config.dropCategories); @@ -183,9 +179,12 @@ private final class ClusterImplLbHelper extends ForwardingLoadBalancerHelper { private long maxConcurrentRequests = DEFAULT_PER_CLUSTER_MAX_CONCURRENT_REQUESTS; @Nullable private SslContextProviderSupplier sslContextProviderSupplier; + @Nullable + private final ServerInfo lrsServerInfo; - private ClusterImplLbHelper(AtomicLong inFlights) { + private ClusterImplLbHelper(AtomicLong inFlights, @Nullable ServerInfo lrsServerInfo) { this.inFlights = checkNotNull(inFlights, "inFlights"); + this.lrsServerInfo = lrsServerInfo; } @Override @@ -218,8 +217,8 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { if (locality == null) { locality = Locality.create("", "", ""); } - final ClusterLocalityStats localityStats = xdsClient.addClusterLocalityStats( - cluster, edsServiceName, locality); + final ClusterLocalityStats localityStats = lrsServerInfo == null ? null + : xdsClient.addClusterLocalityStats(lrsServerInfo, cluster, edsServiceName, locality); Attributes attrs = args.getAttributes().toBuilder().set( ATTR_CLUSTER_LOCALITY_STATS, localityStats).build(); args = args.toBuilder().setAddresses(addresses).setAttributes(attrs).build(); @@ -228,7 +227,9 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { return new ForwardingSubchannel() { @Override public void shutdown() { - localityStats.release(); + if (localityStats != null) { + localityStats.release(); + } delegate().shutdown(); } diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancerProvider.java index 939734fe4f00..11e649474b74 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancerProvider.java @@ -26,6 +26,7 @@ import io.grpc.LoadBalancerRegistry; import io.grpc.NameResolver.ConfigOrError; import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import java.util.ArrayList; @@ -73,9 +74,9 @@ static final class ClusterImplConfig { // Resource name used in discovering endpoints via EDS. Only valid for EDS clusters. @Nullable final String edsServiceName; - // Load report server name. Null if load reporting is disabled. + // Load report server info. Null if load reporting is disabled. @Nullable - final String lrsServerName; + final ServerInfo lrsServerInfo; // Cluster-level max concurrent request threshold. Null if not specified. @Nullable final Long maxConcurrentRequests; @@ -88,12 +89,12 @@ static final class ClusterImplConfig { final PolicySelection childPolicy; ClusterImplConfig(String cluster, @Nullable String edsServiceName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, List dropCategories, PolicySelection childPolicy, @Nullable UpstreamTlsContext tlsContext) { this.cluster = checkNotNull(cluster, "cluster"); this.edsServiceName = edsServiceName; - this.lrsServerName = lrsServerName; + this.lrsServerInfo = lrsServerInfo; this.maxConcurrentRequests = maxConcurrentRequests; this.tlsContext = tlsContext; this.dropCategories = Collections.unmodifiableList( @@ -106,7 +107,7 @@ public String toString() { return MoreObjects.toStringHelper(this) .add("cluster", cluster) .add("edsServiceName", edsServiceName) - .add("lrsServerName", lrsServerName) + .add("lrsServerInfo", lrsServerInfo) .add("maxConcurrentRequests", maxConcurrentRequests) // Exclude tlsContext as its string representation is cumbersome. .add("dropCategories", dropCategories) diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 9ba7541e3142..f4fcabcb21fe 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -39,6 +39,7 @@ import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.GracefulSwitchLoadBalancer; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; @@ -174,10 +175,10 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { ClusterState state; if (instance.type == DiscoveryMechanism.Type.EDS) { state = new EdsClusterState(instance.cluster, instance.edsServiceName, - instance.lrsServerName, instance.maxConcurrentRequests, instance.tlsContext); + instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext); } else { // logical DNS state = new LogicalDnsClusterState(instance.cluster, instance.dnsHostName, - instance.lrsServerName, instance.maxConcurrentRequests, instance.tlsContext); + instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext); } clusterStates.put(instance.cluster, state); state.start(); @@ -305,7 +306,7 @@ private abstract class ClusterState { // Name of the cluster to be resolved. protected final String name; @Nullable - protected final String lrsServerName; + protected final ServerInfo lrsServerInfo; @Nullable protected final Long maxConcurrentRequests; @Nullable @@ -319,10 +320,10 @@ private abstract class ClusterState { protected ClusterResolutionResult result; protected boolean shutdown; - private ClusterState(String name, @Nullable String lrsServerName, + private ClusterState(String name, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { this.name = name; - this.lrsServerName = lrsServerName; + this.lrsServerInfo = lrsServerInfo; this.maxConcurrentRequests = maxConcurrentRequests; this.tlsContext = tlsContext; } @@ -339,9 +340,9 @@ private final class EdsClusterState extends ClusterState implements EdsResourceW private final String edsServiceName; private EdsClusterState(String name, @Nullable String edsServiceName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { - super(name, lrsServerName, maxConcurrentRequests, tlsContext); + super(name, lrsServerInfo, maxConcurrentRequests, tlsContext); this.edsServiceName = edsServiceName; } @@ -423,7 +424,7 @@ public void run() { Collections.sort(priorities); Map priorityChildConfigs = generateEdsBasedPriorityChildConfigs( - name, edsServiceName, lrsServerName, maxConcurrentRequests, tlsContext, + name, edsServiceName, lrsServerInfo, maxConcurrentRequests, tlsContext, endpointLbPolicy, lbRegistry, prioritizedLocalityWeights, dropOverloads); status = Status.OK; resolved = true; @@ -479,9 +480,9 @@ private final class LogicalDnsClusterState extends ClusterState { private ScheduledHandle scheduledRefresh; private LogicalDnsClusterState(String name, String dnsHostName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { - super(name, lrsServerName, maxConcurrentRequests, tlsContext); + super(name, lrsServerInfo, maxConcurrentRequests, tlsContext); this.dnsHostName = checkNotNull(dnsHostName, "dnsHostName"); nameResolverFactory = checkNotNull(helper.getNameResolverRegistry().asFactory(), "nameResolverFactory"); @@ -568,7 +569,7 @@ public void run() { addresses.add(eag); } PriorityChildConfig priorityChildConfig = generateDnsBasedPriorityChildConfig( - name, lrsServerName, maxConcurrentRequests, tlsContext, lbRegistry, + name, lrsServerInfo, maxConcurrentRequests, tlsContext, lbRegistry, Collections.emptyList()); status = Status.OK; resolved = true; @@ -651,14 +652,14 @@ private static class ClusterResolutionResult { *

    priority LB -> cluster_impl LB (single hardcoded priority) -> pick_first */ private static PriorityChildConfig generateDnsBasedPriorityChildConfig( - String cluster, @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + String cluster, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext, LoadBalancerRegistry lbRegistry, List dropOverloads) { // Override endpoint-level LB policy with pick_first for logical DNS cluster. PolicySelection endpointLbPolicy = new PolicySelection(lbRegistry.getProvider("pick_first"), null); ClusterImplConfig clusterImplConfig = - new ClusterImplConfig(cluster, null, lrsServerName, maxConcurrentRequests, + new ClusterImplConfig(cluster, null, lrsServerInfo, maxConcurrentRequests, dropOverloads, endpointLbPolicy, tlsContext); LoadBalancerProvider clusterImplLbProvider = lbRegistry.getProvider(XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME); @@ -674,7 +675,7 @@ private static PriorityChildConfig generateDnsBasedPriorityChildConfig( * -> round_robin (one per locality)) / ring_hash */ private static Map generateEdsBasedPriorityChildConfigs( - String cluster, @Nullable String edsServiceName, @Nullable String lrsServerName, + String cluster, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext, PolicySelection endpointLbPolicy, LoadBalancerRegistry lbRegistry, Map> prioritizedLocalityWeights, @@ -704,7 +705,7 @@ private static Map generateEdsBasedPriorityChildCon leafPolicy = new PolicySelection(weightedTargetLbProvider, weightedTargetConfig); } ClusterImplConfig clusterImplConfig = - new ClusterImplConfig(cluster, edsServiceName, lrsServerName, maxConcurrentRequests, + new ClusterImplConfig(cluster, edsServiceName, lrsServerInfo, maxConcurrentRequests, dropOverloads, leafPolicy, tlsContext); LoadBalancerProvider clusterImplLbProvider = lbRegistry.getProvider(XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME); diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java index 33b150e667b6..551c2c296fc9 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java @@ -25,6 +25,7 @@ import io.grpc.LoadBalancerProvider; import io.grpc.NameResolver.ConfigOrError; import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import java.util.List; import java.util.Map; @@ -107,9 +108,9 @@ static final class DiscoveryMechanism { final String cluster; // Type of the cluster. final Type type; - // Load reporting server name. Null if not enabled. + // Load reporting server info. Null if not enabled. @Nullable - final String lrsServerName; + final ServerInfo lrsServerInfo; // Cluster-level max concurrent request threshold. Null if not specified. @Nullable final Long maxConcurrentRequests; @@ -129,34 +130,34 @@ enum Type { } private DiscoveryMechanism(String cluster, Type type, @Nullable String edsServiceName, - @Nullable String dnsHostName, @Nullable String lrsServerName, + @Nullable String dnsHostName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { this.cluster = checkNotNull(cluster, "cluster"); this.type = checkNotNull(type, "type"); this.edsServiceName = edsServiceName; this.dnsHostName = dnsHostName; - this.lrsServerName = lrsServerName; + this.lrsServerInfo = lrsServerInfo; this.maxConcurrentRequests = maxConcurrentRequests; this.tlsContext = tlsContext; } static DiscoveryMechanism forEds(String cluster, @Nullable String edsServiceName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { - return new DiscoveryMechanism(cluster, Type.EDS, edsServiceName, null, lrsServerName, + return new DiscoveryMechanism(cluster, Type.EDS, edsServiceName, null, lrsServerInfo, maxConcurrentRequests, tlsContext); } static DiscoveryMechanism forLogicalDns(String cluster, String dnsHostName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { return new DiscoveryMechanism(cluster, Type.LOGICAL_DNS, null, dnsHostName, - lrsServerName, maxConcurrentRequests, tlsContext); + lrsServerInfo, maxConcurrentRequests, tlsContext); } @Override public int hashCode() { - return Objects.hash(cluster, type, lrsServerName, maxConcurrentRequests, tlsContext, + return Objects.hash(cluster, type, lrsServerInfo, maxConcurrentRequests, tlsContext, edsServiceName, dnsHostName); } @@ -173,7 +174,7 @@ public boolean equals(Object o) { && type == that.type && Objects.equals(edsServiceName, that.edsServiceName) && Objects.equals(dnsHostName, that.dnsHostName) - && Objects.equals(lrsServerName, that.lrsServerName) + && Objects.equals(lrsServerInfo, that.lrsServerInfo) && Objects.equals(maxConcurrentRequests, that.maxConcurrentRequests) && Objects.equals(tlsContext, that.tlsContext); } @@ -186,7 +187,7 @@ public String toString() { .add("type", type) .add("edsServiceName", edsServiceName) .add("dnsHostName", dnsHostName) - .add("lrsServerName", lrsServerName) + .add("lrsServerInfo", lrsServerInfo) // Exclude tlsContext as its string representation is cumbersome. .add("maxConcurrentRequests", maxConcurrentRequests); return toStringHelper.toString(); diff --git a/xds/src/main/java/io/grpc/xds/CsdsService.java b/xds/src/main/java/io/grpc/xds/CsdsService.java index 5146930859d5..89147536a6d0 100644 --- a/xds/src/main/java/io/grpc/xds/CsdsService.java +++ b/xds/src/main/java/io/grpc/xds/CsdsService.java @@ -21,20 +21,11 @@ import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.util.Timestamps; import io.envoyproxy.envoy.admin.v3.ClientResourceStatus; -import io.envoyproxy.envoy.admin.v3.ClustersConfigDump; -import io.envoyproxy.envoy.admin.v3.ClustersConfigDump.DynamicCluster; -import io.envoyproxy.envoy.admin.v3.EndpointsConfigDump; -import io.envoyproxy.envoy.admin.v3.EndpointsConfigDump.DynamicEndpointConfig; -import io.envoyproxy.envoy.admin.v3.ListenersConfigDump; -import io.envoyproxy.envoy.admin.v3.ListenersConfigDump.DynamicListener; -import io.envoyproxy.envoy.admin.v3.ListenersConfigDump.DynamicListenerState; -import io.envoyproxy.envoy.admin.v3.RoutesConfigDump; -import io.envoyproxy.envoy.admin.v3.RoutesConfigDump.DynamicRouteConfig; import io.envoyproxy.envoy.service.status.v3.ClientConfig; +import io.envoyproxy.envoy.service.status.v3.ClientConfig.GenericXdsConfig; import io.envoyproxy.envoy.service.status.v3.ClientStatusDiscoveryServiceGrpc; import io.envoyproxy.envoy.service.status.v3.ClientStatusRequest; import io.envoyproxy.envoy.service.status.v3.ClientStatusResponse; -import io.envoyproxy.envoy.service.status.v3.PerXdsConfig; import io.grpc.ExperimentalApi; import io.grpc.Status; import io.grpc.StatusException; @@ -150,124 +141,33 @@ private ClientStatusResponse getConfigDumpForRequest(ClientStatusRequest request @VisibleForTesting static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient) { - ListenersConfigDump ldsConfig = dumpLdsConfig( - xdsClient.getSubscribedResourcesMetadata(ResourceType.LDS), - xdsClient.getCurrentVersion(ResourceType.LDS)); - RoutesConfigDump rdsConfig = dumpRdsConfig( - xdsClient.getSubscribedResourcesMetadata(ResourceType.RDS)); - ClustersConfigDump cdsConfig = dumpCdsConfig( - xdsClient.getSubscribedResourcesMetadata(ResourceType.CDS), - xdsClient.getCurrentVersion(ResourceType.CDS)); - EndpointsConfigDump edsConfig = dumpEdsConfig( - xdsClient.getSubscribedResourcesMetadata(ResourceType.EDS)); - - return ClientConfig.newBuilder() - .setNode(xdsClient.getBootstrapInfo().node().toEnvoyProtoNode()) - .addXdsConfig(PerXdsConfig.newBuilder().setListenerConfig(ldsConfig)) - .addXdsConfig(PerXdsConfig.newBuilder().setRouteConfig(rdsConfig)) - .addXdsConfig(PerXdsConfig.newBuilder().setClusterConfig(cdsConfig)) - .addXdsConfig(PerXdsConfig.newBuilder().setEndpointConfig(edsConfig)) - .build(); - } - - @VisibleForTesting - static ListenersConfigDump dumpLdsConfig( - Map resourcesMetadata, String version) { - ListenersConfigDump.Builder ldsConfig = ListenersConfigDump.newBuilder(); - for (Map.Entry entry : resourcesMetadata.entrySet()) { - ldsConfig.addDynamicListeners(buildDynamicListener(entry.getKey(), entry.getValue())); - } - return ldsConfig.setVersionInfo(version).build(); - } - - @VisibleForTesting - static DynamicListener buildDynamicListener(String name, ResourceMetadata metadata) { - DynamicListener.Builder dynamicListener = DynamicListener.newBuilder() - .setName(name) - .setClientStatus(metadataStatusToClientStatus(metadata.getStatus())); - if (metadata.getErrorState() != null) { - dynamicListener.setErrorState(metadataUpdateFailureStateToProto(metadata.getErrorState())); - } - DynamicListenerState.Builder dynamicListenerState = DynamicListenerState.newBuilder() - .setVersionInfo(metadata.getVersion()) - .setLastUpdated(Timestamps.fromNanos(metadata.getUpdateTimeNanos())); - if (metadata.getRawResource() != null) { - dynamicListenerState.setListener(metadata.getRawResource()); - } - return dynamicListener.setActiveState(dynamicListenerState).build(); - } - - @VisibleForTesting - static RoutesConfigDump dumpRdsConfig(Map resourcesMetadata) { - RoutesConfigDump.Builder rdsConfig = RoutesConfigDump.newBuilder(); - for (ResourceMetadata metadata : resourcesMetadata.values()) { - rdsConfig.addDynamicRouteConfigs(buildDynamicRouteConfig(metadata)); - } - return rdsConfig.build(); - } - - @VisibleForTesting - static DynamicRouteConfig buildDynamicRouteConfig(ResourceMetadata metadata) { - DynamicRouteConfig.Builder dynamicRouteConfig = DynamicRouteConfig.newBuilder() - .setVersionInfo(metadata.getVersion()) - .setClientStatus(metadataStatusToClientStatus(metadata.getStatus())) - .setLastUpdated(Timestamps.fromNanos(metadata.getUpdateTimeNanos())); - if (metadata.getErrorState() != null) { - dynamicRouteConfig.setErrorState(metadataUpdateFailureStateToProto(metadata.getErrorState())); - } - if (metadata.getRawResource() != null) { - dynamicRouteConfig.setRouteConfig(metadata.getRawResource()); - } - return dynamicRouteConfig.build(); - } - - @VisibleForTesting - static ClustersConfigDump dumpCdsConfig( - Map resourcesMetadata, String version) { - ClustersConfigDump.Builder cdsConfig = ClustersConfigDump.newBuilder(); - for (ResourceMetadata metadata : resourcesMetadata.values()) { - cdsConfig.addDynamicActiveClusters(buildDynamicCluster(metadata)); - } - return cdsConfig.setVersionInfo(version).build(); - } - - @VisibleForTesting - static DynamicCluster buildDynamicCluster(ResourceMetadata metadata) { - DynamicCluster.Builder dynamicCluster = DynamicCluster.newBuilder() - .setVersionInfo(metadata.getVersion()) - .setClientStatus(metadataStatusToClientStatus(metadata.getStatus())) - .setLastUpdated(Timestamps.fromNanos(metadata.getUpdateTimeNanos())); - if (metadata.getErrorState() != null) { - dynamicCluster.setErrorState(metadataUpdateFailureStateToProto(metadata.getErrorState())); - } - if (metadata.getRawResource() != null) { - dynamicCluster.setCluster(metadata.getRawResource()); - } - return dynamicCluster.build(); - } - - @VisibleForTesting - static EndpointsConfigDump dumpEdsConfig(Map resourcesMetadata) { - EndpointsConfigDump.Builder edsConfig = EndpointsConfigDump.newBuilder(); - for (ResourceMetadata metadata : resourcesMetadata.values()) { - edsConfig.addDynamicEndpointConfigs(buildDynamicEndpointConfig(metadata)); - } - return edsConfig.build(); - } - - @VisibleForTesting - static DynamicEndpointConfig buildDynamicEndpointConfig(ResourceMetadata metadata) { - DynamicEndpointConfig.Builder dynamicRouteConfig = DynamicEndpointConfig.newBuilder() - .setVersionInfo(metadata.getVersion()) - .setClientStatus(metadataStatusToClientStatus(metadata.getStatus())) - .setLastUpdated(Timestamps.fromNanos(metadata.getUpdateTimeNanos())); - if (metadata.getErrorState() != null) { - dynamicRouteConfig.setErrorState(metadataUpdateFailureStateToProto(metadata.getErrorState())); - } - if (metadata.getRawResource() != null) { - dynamicRouteConfig.setEndpointConfig(metadata.getRawResource()); + ClientConfig.Builder builder = ClientConfig.newBuilder() + .setNode(xdsClient.getBootstrapInfo().node().toEnvoyProtoNode()); + for (ResourceType type : ResourceType.values()) { + if (type == ResourceType.UNKNOWN) { + continue; + } + Map metadataMap = xdsClient.getSubscribedResourcesMetadata(type); + for (String resourceName : metadataMap.keySet()) { + ResourceMetadata metadata = metadataMap.get(resourceName); + GenericXdsConfig.Builder genericXdsConfigBuilder = GenericXdsConfig.newBuilder() + .setTypeUrl(type.typeUrl()) + .setName(resourceName) + .setClientStatus(metadataStatusToClientStatus(metadata.getStatus())); + if (metadata.getRawResource() != null) { + genericXdsConfigBuilder + .setVersionInfo(metadata.getVersion()) + .setLastUpdated(Timestamps.fromNanos(metadata.getUpdateTimeNanos())) + .setXdsConfig(metadata.getRawResource()); + } + if (metadata.getStatus() == ResourceMetadataStatus.NACKED) { + genericXdsConfigBuilder + .setErrorState(metadataUpdateFailureStateToProto(metadata.getErrorState())); + } + builder.addGenericXdsConfigs(genericXdsConfigBuilder); + } } - return dynamicRouteConfig.build(); + return builder.build(); } @VisibleForTesting diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java index aa53d834d3bf..09318a8c1504 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -314,7 +314,7 @@ public String toString() { * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.listener.FilterChain}. */ static final class FilterChain { - // Unique name for the FilterChain. + // possibly empty private final String name; // TODO(sanjaypujare): flatten structure by moving FilterChainMatch class members here. private final FilterChainMatch filterChainMatch; diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java index 54fa20128bc0..af2a673e9f7b 100644 --- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java +++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java @@ -28,9 +28,9 @@ import io.envoyproxy.envoy.service.load_stats.v3.LoadReportingServiceGrpc.LoadReportingServiceStub; import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest; import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsResponse; +import io.grpc.Channel; import io.grpc.Context; import io.grpc.InternalLogId; -import io.grpc.ManagedChannel; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; @@ -55,7 +55,7 @@ final class LoadReportClient { private final InternalLogId logId; private final XdsLogger logger; - private final ManagedChannel channel; + private final Channel channel; private final Context context; private final boolean useProtocolV3; private final Node node; @@ -75,7 +75,7 @@ final class LoadReportClient { LoadReportClient( LoadStatsManager2 loadStatsManager, - ManagedChannel channel, + Channel channel, Context context, boolean useProtocolV3, Node node, diff --git a/xds/src/main/java/io/grpc/xds/MessagePrinter.java b/xds/src/main/java/io/grpc/xds/MessagePrinter.java index 249e1f0e9659..a2f51443e3d5 100644 --- a/xds/src/main/java/io/grpc/xds/MessagePrinter.java +++ b/xds/src/main/java/io/grpc/xds/MessagePrinter.java @@ -16,7 +16,9 @@ package io.grpc.xds; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.TypeRegistry; import com.google.protobuf.util.JsonFormat; @@ -46,7 +48,7 @@ private static class LazyHolder { static final JsonFormat.Printer printer = newPrinter(); private static JsonFormat.Printer newPrinter() { - TypeRegistry registry = + TypeRegistry.Builder registry = TypeRegistry.newBuilder() .add(Listener.getDescriptor()) .add(io.envoyproxy.envoy.api.v2.Listener.getDescriptor()) @@ -71,9 +73,20 @@ private static JsonFormat.Printer newPrinter() { .add(io.envoyproxy.envoy.config.cluster.aggregate.v2alpha.ClusterConfig .getDescriptor()) .add(ClusterLoadAssignment.getDescriptor()) - .add(io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.getDescriptor()) - .build(); - return JsonFormat.printer().usingTypeRegistry(registry); + .add(io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.getDescriptor()); + try { + @SuppressWarnings("unchecked") + Class routeLookupClusterSpecifierClass = + (Class) + Class.forName("io.grpc.lookup.v1.RouteLookupClusterSpecifier"); + Descriptor descriptor = + (Descriptor) + routeLookupClusterSpecifierClass.getDeclaredMethod("getDescriptor").invoke(null); + registry.add(descriptor); + } catch (Exception e) { + // Ignore. In most cases RouteLookup is not required. + } + return JsonFormat.printer().usingTypeRegistry(registry.build()); } } diff --git a/xds/src/main/java/io/grpc/xds/OrcaMetricReportingServerInterceptor.java b/xds/src/main/java/io/grpc/xds/OrcaMetricReportingServerInterceptor.java index 8a503bd35f97..9c79ed11bc37 100644 --- a/xds/src/main/java/io/grpc/xds/OrcaMetricReportingServerInterceptor.java +++ b/xds/src/main/java/io/grpc/xds/OrcaMetricReportingServerInterceptor.java @@ -16,7 +16,7 @@ package io.grpc.xds; -import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; +import com.github.xds.data.orca.v3.OrcaLoadReport; import com.google.common.annotations.VisibleForTesting; import io.grpc.Context; import io.grpc.Contexts; diff --git a/xds/src/main/java/io/grpc/xds/OrcaOobUtil.java b/xds/src/main/java/io/grpc/xds/OrcaOobUtil.java index 1b5acd8c080f..8970a68bf652 100644 --- a/xds/src/main/java/io/grpc/xds/OrcaOobUtil.java +++ b/xds/src/main/java/io/grpc/xds/OrcaOobUtil.java @@ -22,9 +22,9 @@ import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.SHUTDOWN; -import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; -import com.github.udpa.udpa.service.orca.v1.OpenRcaServiceGrpc; -import com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest; +import com.github.xds.data.orca.v3.OrcaLoadReport; +import com.github.xds.service.orca.v3.OpenRcaServiceGrpc; +import com.github.xds.service.orca.v3.OrcaLoadReportRequest; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; diff --git a/xds/src/main/java/io/grpc/xds/OrcaPerRequestUtil.java b/xds/src/main/java/io/grpc/xds/OrcaPerRequestUtil.java index 156d53f638ed..4b4cff920646 100644 --- a/xds/src/main/java/io/grpc/xds/OrcaPerRequestUtil.java +++ b/xds/src/main/java/io/grpc/xds/OrcaPerRequestUtil.java @@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; +import com.github.xds.data.orca.v3.OrcaLoadReport; import com.google.common.annotations.VisibleForTesting; import io.grpc.CallOptions; import io.grpc.ClientStreamTracer; diff --git a/xds/src/main/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPlugin.java b/xds/src/main/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPlugin.java index 8bec286925f6..64464f037372 100644 --- a/xds/src/main/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPlugin.java +++ b/xds/src/main/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPlugin.java @@ -17,18 +17,14 @@ package io.grpc.xds; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import com.google.protobuf.util.Durations; -import io.grpc.lookup.v1.GrpcKeyBuilder; -import io.grpc.lookup.v1.GrpcKeyBuilder.ExtraKeys; -import io.grpc.lookup.v1.GrpcKeyBuilder.Name; -import io.grpc.lookup.v1.NameMatcher; -import io.grpc.lookup.v1.RouteLookupConfig; -import io.grpc.rls.RlsProtoData; -import java.util.ArrayList; -import java.util.List; +import io.grpc.internal.JsonParser; +import io.grpc.internal.JsonUtil; +import java.io.IOException; +import java.util.Map; /** The ClusterSpecifierPlugin for RouteLookup policy. */ final class RouteLookupServiceClusterSpecifierPlugin implements ClusterSpecifierPlugin { @@ -49,84 +45,49 @@ public String[] typeUrls() { } @Override + @SuppressWarnings("unchecked") public ConfigOrError parsePlugin(Message rawProtoMessage) { if (!(rawProtoMessage instanceof Any)) { return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass()); } - - Any anyMessage = (Any) rawProtoMessage; - RouteLookupConfig configProto; - try { - configProto = anyMessage.unpack(RouteLookupConfig.class); - } catch (InvalidProtocolBufferException e) { - return ConfigOrError.fromError("Invalid proto: " + e); - } try { - List keyBuildersProto = configProto.getGrpcKeybuildersList(); - List keyBuilders = - new ArrayList<>(keyBuildersProto.size()); - for (GrpcKeyBuilder keyBuilderProto : keyBuildersProto) { - List namesProto = keyBuilderProto.getNamesList(); - List names = new ArrayList<>(namesProto.size()); - for (Name nameProto : namesProto) { - if (nameProto.getMethod().isEmpty()) { - names.add(new RlsProtoData.GrpcKeyBuilder.Name(nameProto.getService())); - } else { - names.add( - new RlsProtoData.GrpcKeyBuilder.Name( - nameProto.getService(), nameProto.getMethod())); - } - } - - List headersProto = keyBuilderProto.getHeadersList(); - List headers = new ArrayList<>(headersProto.size()); - for (NameMatcher headerProto : headersProto) { - headers.add( - new RlsProtoData.NameMatcher( - headerProto.getKey(), headerProto.getNamesList(), - headerProto.getRequiredMatch())); - } - - String host = null; - String service = null; - String method = null; - if (keyBuilderProto.hasExtraKeys()) { - ExtraKeys extraKeysProto = keyBuilderProto.getExtraKeys(); - host = extraKeysProto.getHost(); - service = extraKeysProto.getService(); - method = extraKeysProto.getMethod(); - } - RlsProtoData.ExtraKeys extraKeys = - RlsProtoData.ExtraKeys.create(host, service, method); - - RlsProtoData.GrpcKeyBuilder keyBuilder = - new RlsProtoData.GrpcKeyBuilder( - names, headers, extraKeys, keyBuilderProto.getConstantKeysMap()); - keyBuilders.add(keyBuilder); + Any anyMessage = (Any) rawProtoMessage; + Class protoClass; + try { + protoClass = + (Class) + Class.forName("io.grpc.lookup.v1.RouteLookupClusterSpecifier"); + } catch (ClassNotFoundException e) { + return ConfigOrError.fromError("Dependency for 'io.grpc:grpc-rls' is missing: " + e); + } + Message configProto; + try { + configProto = anyMessage.unpack(protoClass); + } catch (InvalidProtocolBufferException e) { + return ConfigOrError.fromError("Invalid proto: " + e); + } + String jsonString = MessagePrinter.print(configProto); + try { + Map jsonMap = (Map) JsonParser.parse(jsonString); + Map config = JsonUtil.getObject(jsonMap, "routeLookupConfig"); + return ConfigOrError.fromConfig(RlsPluginConfig.create(config)); + } catch (IOException e) { + return ConfigOrError.fromError( + "Unable to parse RouteLookupClusterSpecifier: " + jsonString); } - RlsProtoData.RouteLookupConfig config = new RlsProtoData.RouteLookupConfig( - keyBuilders, - configProto.getLookupService(), - Durations.toMillis(configProto.getLookupServiceTimeout()), - configProto.hasMaxAge() ? Durations.toMillis(configProto.getMaxAge()) : null, - configProto.hasStaleAge() ? Durations.toMillis(configProto.getStaleAge()) : null, - configProto.getCacheSizeBytes(), - configProto.getValidTargetsList(), - configProto.getDefaultTarget()); - return ConfigOrError.fromConfig(RlsPluginConfig.create(config)); } catch (RuntimeException e) { - return ConfigOrError.fromError( - "Error parsing RouteLookupConfig: \n" + configProto + "\n reason: " + e); + return ConfigOrError.fromError("Error parsing RouteLookupConfig: " + e); } } @AutoValue abstract static class RlsPluginConfig implements PluginConfig { - abstract RlsProtoData.RouteLookupConfig config(); + abstract ImmutableMap config(); - static RlsPluginConfig create(RlsProtoData.RouteLookupConfig config) { - return new AutoValue_RouteLookupServiceClusterSpecifierPlugin_RlsPluginConfig(config); + static RlsPluginConfig create(Map config) { + return new AutoValue_RouteLookupServiceClusterSpecifierPlugin_RlsPluginConfig( + ImmutableMap.copyOf(config)); } @Override diff --git a/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java b/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java index 14bdced5dac5..1c8fe0bad6d2 100644 --- a/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java +++ b/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java @@ -19,22 +19,18 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; -import io.grpc.ChannelCredentials; import io.grpc.Context; -import io.grpc.Grpc; -import io.grpc.ManagedChannel; import io.grpc.internal.ExponentialBackoffPolicy; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourceHolder; import io.grpc.internal.TimeProvider; import io.grpc.xds.Bootstrapper.BootstrapInfo; -import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.ClientXdsClient.XdsChannelFactory; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import io.grpc.xds.internal.sds.TlsContextManagerImpl; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -113,8 +109,6 @@ static class RefCountedXdsClientObjectPool implements ObjectPool { @GuardedBy("lock") private ScheduledExecutorService scheduler; @GuardedBy("lock") - private ManagedChannel channel; - @GuardedBy("lock") private XdsClient xdsClient; @GuardedBy("lock") private int refCount; @@ -128,16 +122,16 @@ static class RefCountedXdsClientObjectPool implements ObjectPool { public XdsClient getObject() { synchronized (lock) { if (refCount == 0) { - ServerInfo serverInfo = bootstrapInfo.servers().get(0); // use first server - String target = serverInfo.target(); - ChannelCredentials channelCredentials = serverInfo.channelCredentials(); - channel = Grpc.newChannelBuilder(target, channelCredentials) - .keepAliveTime(5, TimeUnit.MINUTES) - .build(); scheduler = SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE); - xdsClient = new ClientXdsClient(channel, bootstrapInfo, context, scheduler, - new ExponentialBackoffPolicy.Provider(), GrpcUtil.STOPWATCH_SUPPLIER, - TimeProvider.SYSTEM_TIME_PROVIDER, new TlsContextManagerImpl(bootstrapInfo)); + xdsClient = new ClientXdsClient( + XdsChannelFactory.DEFAULT_XDS_CHANNEL_FACTORY, + bootstrapInfo, + context, + scheduler, + new ExponentialBackoffPolicy.Provider(), + GrpcUtil.STOPWATCH_SUPPLIER, + TimeProvider.SYSTEM_TIME_PROVIDER, + new TlsContextManagerImpl(bootstrapInfo)); } refCount++; return xdsClient; @@ -151,21 +145,12 @@ public XdsClient returnObject(Object object) { if (refCount == 0) { xdsClient.shutdown(); xdsClient = null; - channel.shutdown(); scheduler = SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, scheduler); } return null; } } - @VisibleForTesting - @Nullable - ManagedChannel getChannelForTest() { - synchronized (lock) { - return channel; - } - } - @VisibleForTesting @Nullable XdsClient getXdsClientForTest() { diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 6b6be57f0429..65d4d67c427c 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -24,6 +24,7 @@ import com.google.protobuf.Any; import io.grpc.Status; import io.grpc.xds.AbstractXdsClient.ResourceType; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LocalityLbEndpoints; import io.grpc.xds.EnvoyServerProtoData.Listener; @@ -31,6 +32,7 @@ import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -128,10 +130,10 @@ abstract static class CdsUpdate implements ResourceUpdate { @Nullable abstract String dnsHostName(); - // Load report server name for reporting loads via LRS. + // Load report server info for reporting loads via LRS. // Only valid for EDS or LOGICAL_DNS cluster. @Nullable - abstract String lrsServerName(); + abstract ServerInfo lrsServerInfo(); // Max number of concurrent requests can be sent to this cluster. // Only valid for EDS or LOGICAL_DNS cluster. @@ -159,7 +161,7 @@ static Builder forAggregate(String clusterName, List prioritizedClusterN } static Builder forEds(String clusterName, @Nullable String edsServiceName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext upstreamTlsContext) { return new AutoValue_XdsClient_CdsUpdate.Builder() .clusterName(clusterName) @@ -167,13 +169,13 @@ static Builder forEds(String clusterName, @Nullable String edsServiceName, .minRingSize(0) .maxRingSize(0) .edsServiceName(edsServiceName) - .lrsServerName(lrsServerName) + .lrsServerInfo(lrsServerInfo) .maxConcurrentRequests(maxConcurrentRequests) .upstreamTlsContext(upstreamTlsContext); } static Builder forLogicalDns(String clusterName, String dnsHostName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext upstreamTlsContext) { return new AutoValue_XdsClient_CdsUpdate.Builder() .clusterName(clusterName) @@ -181,7 +183,7 @@ static Builder forLogicalDns(String clusterName, String dnsHostName, .minRingSize(0) .maxRingSize(0) .dnsHostName(dnsHostName) - .lrsServerName(lrsServerName) + .lrsServerInfo(lrsServerInfo) .maxConcurrentRequests(maxConcurrentRequests) .upstreamTlsContext(upstreamTlsContext); } @@ -205,7 +207,7 @@ public final String toString() { .add("maxRingSize", maxRingSize()) .add("edsServiceName", edsServiceName()) .add("dnsHostName", dnsHostName()) - .add("lrsServerName", lrsServerName()) + .add("lrsServerInfo", lrsServerInfo()) .add("maxConcurrentRequests", maxConcurrentRequests()) // Exclude upstreamTlsContext as its string representation is cumbersome. .add("prioritizedClusterNames", prioritizedClusterNames()) @@ -244,7 +246,7 @@ Builder ringHashLbPolicy(long minRingSize, long maxRingSize) { protected abstract Builder dnsHostName(String dnsHostName); // Private, use one of the static factory methods instead. - protected abstract Builder lrsServerName(String lrsServerName); + protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo); // Private, use one of the static factory methods instead. protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests); @@ -492,13 +494,6 @@ TlsContextManager getTlsContextManager() { throw new UnsupportedOperationException(); } - /** - * Returns the latest accepted version of the given resource type. - */ - String getCurrentVersion(ResourceType type) { - throw new UnsupportedOperationException(); - } - Map getSubscribedResourcesMetadata(ResourceType type) { throw new UnsupportedOperationException(); } @@ -566,7 +561,8 @@ void cancelEdsResourceWatch(String resourceName, EdsResourceWatcher watcher) { * use {@link ClusterDropStats#release} to release its hard reference when it is safe to * stop reporting dropped RPCs for the specified cluster in the future. */ - ClusterDropStats addClusterDropStats(String clusterName, @Nullable String edsServiceName) { + ClusterDropStats addClusterDropStats( + ServerInfo serverInfo, String clusterName, @Nullable String edsServiceName) { throw new UnsupportedOperationException(); } @@ -579,7 +575,47 @@ ClusterDropStats addClusterDropStats(String clusterName, @Nullable String edsSer * future. */ ClusterLocalityStats addClusterLocalityStats( - String clusterName, @Nullable String edsServiceName, Locality locality) { + ServerInfo serverInfo, String clusterName, @Nullable String edsServiceName, + Locality locality) { throw new UnsupportedOperationException(); } + + interface XdsResponseHandler { + /** Called when an LDS response is received. */ + void handleLdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce); + + /** Called when an RDS response is received. */ + void handleRdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce); + + /** Called when an CDS response is received. */ + void handleCdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce); + + /** Called when an EDS response is received. */ + void handleEdsResponse( + ServerInfo serverInfo, String versionInfo, List resources, String nonce); + + /** Called when the ADS stream is closed passively. */ + // Must be synchronized. + void handleStreamClosed(Status error); + + /** Called when the ADS stream has been recreated. */ + // Must be synchronized. + void handleStreamRestarted(ServerInfo serverInfo); + } + + interface ResourceStore { + /** + * Returns the collection of resources currently subscribing to or {@code null} if not + * subscribing to any resources for the given type. + * + *

    Note an empty collection indicates subscribing to resources of the given type with + * wildcard mode. + */ + // Must be synchronized. + @Nullable + Collection getSubscribedResources(ServerInfo serverInfo, ResourceType type); + } } diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index ed51ccc9edf3..dab3cd798f79 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -571,10 +571,6 @@ private void handleConfigNotFound(StatusException exception) { for (SslContextProviderSupplier s: toRelease) { s.close(); } - if (!initialStarted) { - initialStarted = true; - initialStartFuture.set(exception); - } if (restartTimer != null) { restartTimer.cancel(); } diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index 24586c70e91c..388fc6a8b209 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -29,6 +29,7 @@ import io.grpc.Attributes; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; +import io.grpc.InsecureChannelCredentials; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.PickResult; @@ -42,6 +43,7 @@ import io.grpc.Status.Code; import io.grpc.SynchronizationContext; import io.grpc.internal.ObjectPool; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; @@ -75,7 +77,8 @@ public class CdsLoadBalancer2Test { private static final String CLUSTER = "cluster-foo.googleapis.com"; private static final String EDS_SERVICE_NAME = "backend-service-1.googleapis.com"; private static final String DNS_HOST_NAME = "backend-service-dns.googleapis.com:443"; - private static final String LRS_SERVER_NAME = "lrs.googleapis.com"; + private static final ServerInfo LRS_SERVER_INFO = + ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create(), true); private final UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true); @@ -143,7 +146,7 @@ public void tearDown() { @Test public void discoverTopLevelEdsCluster() { CdsUpdate update = - CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, 100L, upstreamTlsContext) + CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(childBalancers).hasSize(1); @@ -153,14 +156,14 @@ public void discoverTopLevelEdsCluster() { assertThat(childLbConfig.discoveryMechanisms).hasSize(1); DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, - null, LRS_SERVER_NAME, 100L, upstreamTlsContext); + null, LRS_SERVER_INFO, 100L, upstreamTlsContext); assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()).isEqualTo("round_robin"); } @Test public void discoverTopLevelLogicalDnsCluster() { CdsUpdate update = - CdsUpdate.forLogicalDns(CLUSTER, DNS_HOST_NAME, LRS_SERVER_NAME, 100L, upstreamTlsContext) + CdsUpdate.forLogicalDns(CLUSTER, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(childBalancers).hasSize(1); @@ -170,7 +173,7 @@ public void discoverTopLevelLogicalDnsCluster() { assertThat(childLbConfig.discoveryMechanisms).hasSize(1); DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.LOGICAL_DNS, null, - DNS_HOST_NAME, LRS_SERVER_NAME, 100L, upstreamTlsContext); + DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext); assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()).isEqualTo("round_robin"); } @@ -198,13 +201,13 @@ public void nonAggregateCluster_resourceUpdate() { assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, null, null, null, 100L, upstreamTlsContext); - update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, 200L, null) + update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, null) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); childLbConfig = (ClusterResolverConfig) childBalancer.config; instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, - null, LRS_SERVER_NAME, 200L, null); + null, LRS_SERVER_INFO, 200L, null); } @Test @@ -253,7 +256,7 @@ public void discoverAggregateCluster() { CLUSTER, cluster1, cluster2, cluster3, cluster4); assertThat(childBalancers).isEmpty(); CdsUpdate update3 = - CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_NAME, 200L, upstreamTlsContext) + CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster3, update3); assertThat(childBalancers).isEmpty(); @@ -263,7 +266,7 @@ public void discoverAggregateCluster() { xdsClient.deliverCdsUpdate(cluster2, update2); assertThat(childBalancers).isEmpty(); CdsUpdate update4 = - CdsUpdate.forEds(cluster4, null, LRS_SERVER_NAME, 300L, null) + CdsUpdate.forEds(cluster4, null, LRS_SERVER_INFO, 300L, null) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster4, update4); assertThat(childBalancers).hasSize(1); // all non-aggregate clusters discovered @@ -275,10 +278,10 @@ public void discoverAggregateCluster() { assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster2, DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, null, 100L, null); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster3, - DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_NAME, 200L, + DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L, upstreamTlsContext); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(2), cluster4, - DiscoveryMechanism.Type.EDS, null, null, LRS_SERVER_NAME, 300L, null); + DiscoveryMechanism.Type.EDS, null, null, LRS_SERVER_INFO, 300L, null); assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()) .isEqualTo("ring_hash"); // dominated by top-level cluster's config assertThat(((RingHashConfig) childLbConfig.lbPolicy.getConfig()).minRingSize).isEqualTo(100L); @@ -314,21 +317,21 @@ public void aggregateCluster_descendantClustersRevoked() { xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster1, cluster2); CdsUpdate update1 = - CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_NAME, 200L, upstreamTlsContext) + CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster1, update1); CdsUpdate update2 = - CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_NAME, 100L, null) + CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster2, update2); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; assertThat(childLbConfig.discoveryMechanisms).hasSize(2); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster1, - DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_NAME, 200L, + DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L, upstreamTlsContext); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_NAME, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null); // Revoke cluster1, should still be able to proceed with cluster2. xdsClient.deliverResourceNotExist(cluster1); @@ -336,7 +339,7 @@ public void aggregateCluster_descendantClustersRevoked() { childLbConfig = (ClusterResolverConfig) childBalancer.config; assertThat(childLbConfig.discoveryMechanisms).hasSize(1); assertDiscoveryMechanism(Iterables.getOnlyElement(childLbConfig.discoveryMechanisms), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_NAME, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null); verify(helper, never()).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), any(SubchannelPicker.class)); @@ -362,21 +365,21 @@ public void aggregateCluster_rootClusterRevoked() { xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster1, cluster2); CdsUpdate update1 = - CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_NAME, 200L, upstreamTlsContext) + CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster1, update1); CdsUpdate update2 = - CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_NAME, 100L, null) + CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster2, update2); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; assertThat(childLbConfig.discoveryMechanisms).hasSize(2); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster1, - DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_NAME, 200L, + DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L, upstreamTlsContext); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_NAME, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null); xdsClient.deliverResourceNotExist(CLUSTER); assertThat(xdsClient.watchers.keySet()) @@ -416,7 +419,7 @@ public void aggregateCluster_intermediateClusterChanges() { xdsClient.deliverCdsUpdate(cluster2, update2); assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster2, cluster3); CdsUpdate update3 = - CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_NAME, 100L, upstreamTlsContext) + CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster3, update3); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); @@ -424,7 +427,7 @@ public void aggregateCluster_intermediateClusterChanges() { assertThat(childLbConfig.discoveryMechanisms).hasSize(1); DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, cluster3, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, - null, LRS_SERVER_NAME, 100L, upstreamTlsContext); + null, LRS_SERVER_INFO, 100L, upstreamTlsContext); // cluster2 revoked xdsClient.deliverResourceNotExist(cluster2); @@ -465,7 +468,7 @@ public void aggregateCluster_discoveryErrorAfterChildLbCreated_propagateToChildL .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); CdsUpdate update1 = - CdsUpdate.forLogicalDns(cluster1, DNS_HOST_NAME, LRS_SERVER_NAME, 200L, null) + CdsUpdate.forLogicalDns(cluster1, DNS_HOST_NAME, LRS_SERVER_INFO, 200L, null) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster1, update1); FakeLoadBalancer childLb = Iterables.getOnlyElement(childBalancers); @@ -490,7 +493,7 @@ public void handleNameResolutionErrorFromUpstream_beforeChildLbCreated_returnErr @Test public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThrough() { CdsUpdate update = - CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, 100L, upstreamTlsContext) + CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); @@ -515,13 +518,13 @@ private static void assertPicker(SubchannelPicker picker, Status expectedStatus, private static void assertDiscoveryMechanism(DiscoveryMechanism instance, String name, DiscoveryMechanism.Type type, @Nullable String edsServiceName, @Nullable String dnsHostName, - @Nullable String lrsServerName, @Nullable Long maxConcurrentRequests, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { assertThat(instance.cluster).isEqualTo(name); assertThat(instance.type).isEqualTo(type); assertThat(instance.edsServiceName).isEqualTo(edsServiceName); assertThat(instance.dnsHostName).isEqualTo(dnsHostName); - assertThat(instance.lrsServerName).isEqualTo(lrsServerName); + assertThat(instance.lrsServerInfo).isEqualTo(lrsServerInfo); assertThat(instance.maxConcurrentRequests).isEqualTo(maxConcurrentRequests); assertThat(instance.tlsContext).isEqualTo(tlsContext); } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index 0207c2b94e5a..e14d6db45ac8 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -18,14 +18,18 @@ import static com.google.common.truth.Truth.assertThat; +import com.github.xds.type.v3.TypedStruct; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Any; import com.google.protobuf.BoolValue; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; +import com.google.protobuf.Value; import com.google.protobuf.util.Durations; import com.google.re2j.Pattern; import io.envoyproxy.envoy.config.cluster.v3.Cluster; @@ -94,7 +98,11 @@ import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; import io.envoyproxy.envoy.type.v3.Int64Range; +import io.grpc.ClientInterceptor; +import io.grpc.InsecureChannelCredentials; +import io.grpc.LoadBalancer; import io.grpc.Status.Code; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClientXdsClient.ResourceInvalidException; import io.grpc.xds.ClientXdsClient.StructOrError; import io.grpc.xds.Endpoints.LbEndpoint; @@ -114,7 +122,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -126,6 +136,9 @@ @RunWith(JUnit4.class) public class ClientXdsClientDataTest { + private static final ServerInfo LRS_SERVER_INFO = + ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create(), true); + @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467 @Rule public final ExpectedException thrown = ExpectedException.none(); @@ -874,6 +887,107 @@ public void parseHttpFilter_unsupportedButOptional() { assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true)).isNull(); } + private static class SimpleFilterConfig implements FilterConfig { + private final Message message; + + public SimpleFilterConfig(Message rawProtoMessage) { + message = rawProtoMessage; + } + + public Message getConfig() { + return message; + } + + @Override + public String typeUrl() { + return null; + } + } + + private static class TestFilter implements io.grpc.xds.Filter, + io.grpc.xds.Filter.ClientInterceptorBuilder { + @Override + public String[] typeUrls() { + return new String[]{"test-url"}; + } + + @Override + public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + return ConfigOrError.fromConfig(new SimpleFilterConfig(rawProtoMessage)); + } + + @Override + public ConfigOrError parseFilterConfigOverride( + Message rawProtoMessage) { + return ConfigOrError.fromConfig(new SimpleFilterConfig(rawProtoMessage)); + } + + @Nullable + @Override + public ClientInterceptor buildClientInterceptor(FilterConfig config, + @Nullable FilterConfig overrideConfig, + LoadBalancer.PickSubchannelArgs args, + ScheduledExecutorService scheduler) { + return null; + } + } + + @Test + public void parseHttpFilter_typedStructMigration() { + filterRegistry.register(new TestFilter()); + Struct rawStruct = Struct.newBuilder() + .putFields("name", Value.newBuilder().setStringValue("default").build()) + .build(); + HttpFilter httpFilter = HttpFilter.newBuilder() + .setIsOptional(true) + .setTypedConfig(Any.pack( + com.github.udpa.udpa.type.v1.TypedStruct.newBuilder() + .setTypeUrl("test-url") + .setValue(rawStruct) + .build())).build(); + FilterConfig config = ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, + true).getStruct(); + assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); + + HttpFilter httpFilterNewTypeStruct = HttpFilter.newBuilder() + .setIsOptional(true) + .setTypedConfig(Any.pack( + TypedStruct.newBuilder() + .setTypeUrl("test-url") + .setValue(rawStruct) + .build())).build(); + config = ClientXdsClient.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry, + true).getStruct(); + assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); + } + + @Test + public void parseOverrideHttpFilter_typedStructMigration() { + filterRegistry.register(new TestFilter()); + Struct rawStruct0 = Struct.newBuilder() + .putFields("name", Value.newBuilder().setStringValue("default0").build()) + .build(); + Struct rawStruct1 = Struct.newBuilder() + .putFields("name", Value.newBuilder().setStringValue("default1").build()) + .build(); + Map rawFilterMap = ImmutableMap.of( + "struct-0", Any.pack( + com.github.udpa.udpa.type.v1.TypedStruct.newBuilder() + .setTypeUrl("test-url") + .setValue(rawStruct0) + .build()), + "struct-1", Any.pack( + TypedStruct.newBuilder() + .setTypeUrl("test-url") + .setValue(rawStruct1) + .build()) + ); + Map map = ClientXdsClient.parseOverrideFilterConfigs(rawFilterMap, + filterRegistry).getStruct(); + assertThat(((SimpleFilterConfig)map.get("struct-0")).getConfig()).isEqualTo(rawStruct0); + assertThat(((SimpleFilterConfig)map.get("struct-1")).getConfig()).isEqualTo(rawStruct1); + } + @Test public void parseHttpFilter_unsupportedAndRequired() { HttpFilter httpFilter = HttpFilter.newBuilder() @@ -1239,7 +1353,8 @@ public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInval .setLbPolicy(LbPolicy.RING_HASH) .build(); - CdsUpdate update = ClientXdsClient.parseCluster(cluster, new HashSet(), null); + CdsUpdate update = ClientXdsClient.parseCluster( + cluster, new HashSet(), null, LRS_SERVER_INFO); assertThat(update.lbPolicy()).isEqualTo(CdsUpdate.LbPolicy.RING_HASH); assertThat(update.minRingSize()) .isEqualTo(ClientXdsClient.DEFAULT_RING_HASH_LB_POLICY_MIN_RING_SIZE); @@ -1266,7 +1381,7 @@ public void parseCluster_transportSocketMatches_exception() throws ResourceInval thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "Cluster cluster-foo.googleapis.com: transport-socket-matches not supported."); - ClientXdsClient.parseCluster(cluster, new HashSet(), null); + ClientXdsClient.parseCluster(cluster, new HashSet(), null, LRS_SERVER_INFO); } @Test @@ -1291,7 +1406,7 @@ public void parseCluster_ringHashLbPolicy_invalidRingSizeConfig_minGreaterThanMa thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Cluster cluster-foo.googleapis.com: invalid ring_hash_lb_config"); - ClientXdsClient.parseCluster(cluster, new HashSet(), null); + ClientXdsClient.parseCluster(cluster, new HashSet(), null, LRS_SERVER_INFO); } @Test @@ -1318,7 +1433,7 @@ public void parseCluster_ringHashLbPolicy_invalidRingSizeConfig_tooLargeRingSize thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Cluster cluster-foo.googleapis.com: invalid ring_hash_lb_config"); - ClientXdsClient.parseCluster(cluster, new HashSet(), null); + ClientXdsClient.parseCluster(cluster, new HashSet(), null, LRS_SERVER_INFO); } @Test @@ -1587,7 +1702,7 @@ public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException } @Test - public void parseFilterChain_noName_generatedUuid() throws ResourceInvalidException { + public void parseFilterChain_noName() throws ResourceInvalidException { FilterChain filterChain1 = FilterChain.newBuilder() .setFilterChainMatch(FilterChainMatch.getDefaultInstance()) @@ -1615,7 +1730,7 @@ public void parseFilterChain_noName_generatedUuid() throws ResourceInvalidExcept EnvoyServerProtoData.FilterChain parsedFilterChain2 = ClientXdsClient.parseFilterChain( filterChain2, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); - assertThat(parsedFilterChain1.getName()).isNotEqualTo(parsedFilterChain2.getName()); + assertThat(parsedFilterChain1.getName()).isEqualTo(parsedFilterChain2.getName()); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index 5fa9e3da734a..9d0c735ba959 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -43,6 +43,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.grpc.BindableService; +import io.grpc.ChannelCredentials; import io.grpc.Context; import io.grpc.Context.CancellableContext; import io.grpc.InsecureChannelCredentials; @@ -59,6 +60,8 @@ import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; +import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.ClientXdsClient.XdsChannelFactory; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints; @@ -122,6 +125,9 @@ public abstract class ClientXdsClientTestBase { private static final String VERSION_3 = "44"; private static final Node NODE = Node.newBuilder().build(); private static final Any FAILING_ANY = MessageFactory.FAILING_ANY; + private static final ChannelCredentials CHANNEL_CREDENTIALS = InsecureChannelCredentials.create(); + private final ServerInfo lrsServerInfo = + ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, useProtocolV3()); private static final FakeClock.TaskFilter RPC_RETRY_TASK_FILTER = new FakeClock.TaskFilter() { @@ -272,19 +278,24 @@ public void setUp() throws IOException { .start()); channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); + XdsChannelFactory xdsChannelFactory = new XdsChannelFactory() { + @Override + ManagedChannel create(ServerInfo serverInfo) { + return channel; + } + }; Bootstrapper.BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Arrays.asList( - Bootstrapper.ServerInfo.create( - SERVER_URI, InsecureChannelCredentials.create(), useProtocolV3()))) + Bootstrapper.ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, useProtocolV3()))) .node(EnvoyProtoData.Node.newBuilder().build()) .certProviders(ImmutableMap.of("cert-instance-name", CertificateProviderInfo.create("file-watcher", ImmutableMap.of()))) .build(); xdsClient = new ClientXdsClient( - channel, + xdsChannelFactory, bootstrapInfo, Context.ROOT, fakeClock.getScheduledExecutorService(), @@ -1385,7 +1396,7 @@ public void cdsResourceFound() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isNull(); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); @@ -1414,7 +1425,7 @@ public void cdsResourceFound_ringHashLbPolicy() { assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.RING_HASH); assertThat(cdsUpdate.minRingSize()).isEqualTo(10L); assertThat(cdsUpdate.maxRingSize()).isEqualTo(100L); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); @@ -1459,7 +1470,7 @@ public void cdsResponseWithCircuitBreakers() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isNull(); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isEqualTo(200L); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterCircuitBreakers, VERSION_1, @@ -1555,12 +1566,16 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() { call.sendResponse(CDS, clusters, VERSION_1, "0000"); // The response NACKed with errors indicating indices of the failed resources. - call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of( - "CDS response Cluster 'cluster.googleapis.com' validation error: " + String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: " + "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: " + "io.grpc.xds.ClientXdsClient$ResourceInvalidException: " - + "ca_certificate_provider_instance is required in upstream-tls-context")); - verifyNoInteractions(cdsResourceWatcher); + + "ca_certificate_provider_instance is required in upstream-tls-context"; + call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Status.class); + verify(cdsResourceWatcher).onError(captor.capture()); + Status errorStatus = captor.getValue(); + assertThat(errorStatus.getCode()).isEqualTo(Status.UNAVAILABLE.getCode()); + assertThat(errorStatus.getDescription()).isEqualTo(errorMsg); } /** @@ -1579,10 +1594,14 @@ public void cdsResponseErrorHandling_badTransportSocketName() { call.sendResponse(CDS, clusters, VERSION_1, "0000"); // The response NACKed with errors indicating indices of the failed resources. - call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of( - "CDS response Cluster 'cluster.googleapis.com' validation error: " - + "transport-socket with name envoy.transport_sockets.bad not supported.")); - verifyNoInteractions(cdsResourceWatcher); + String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: " + + "transport-socket with name envoy.transport_sockets.bad not supported."; + call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Status.class); + verify(cdsResourceWatcher).onError(captor.capture()); + Status errorStatus = captor.getValue(); + assertThat(errorStatus.getCode()).isEqualTo(Status.UNAVAILABLE.getCode()); + assertThat(errorStatus.getDescription()).isEqualTo(errorMsg); } @Test @@ -1601,7 +1620,7 @@ public void cachedCdsResource_data() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isNull(); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); call.verifyNoMoreRequest(); @@ -1643,7 +1662,7 @@ public void cdsResourceUpdated() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.LOGICAL_DNS); assertThat(cdsUpdate.dnsHostName()).isEqualTo(dnsHostAddr + ":" + dnsHostPort); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterDns, VERSION_1, TIME_INCREMENT); @@ -1662,7 +1681,7 @@ public void cdsResourceUpdated() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isEqualTo(edsService); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isEqualTo(""); + assertThat(cdsUpdate.lrsServerInfo()).isEqualTo(lrsServerInfo); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterEds, VERSION_2, TIME_INCREMENT * 2); @@ -1683,7 +1702,7 @@ public void cdsResourceDeleted() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isNull(); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); verifyResourceMetadataAcked(CDS, CDS_RESOURCE, testClusterRoundRobin, VERSION_1, @@ -1735,7 +1754,7 @@ public void multipleCdsWatchers() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.LOGICAL_DNS); assertThat(cdsUpdate.dnsHostName()).isEqualTo(dnsHostAddr + ":" + dnsHostPort); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); verify(watcher1).onChanged(cdsUpdateCaptor.capture()); @@ -1744,7 +1763,7 @@ public void multipleCdsWatchers() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isEqualTo(edsService); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isEqualTo(""); + assertThat(cdsUpdate.lrsServerInfo()).isEqualTo(lrsServerInfo); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); verify(watcher2).onChanged(cdsUpdateCaptor.capture()); @@ -1753,7 +1772,7 @@ public void multipleCdsWatchers() { assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); assertThat(cdsUpdate.edsServiceName()).isEqualTo(edsService); assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.ROUND_ROBIN); - assertThat(cdsUpdate.lrsServerName()).isEqualTo(""); + assertThat(cdsUpdate.lrsServerInfo()).isEqualTo(lrsServerInfo); assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); assertThat(cdsUpdate.upstreamTlsContext()).isNull(); // Metadata of both clusters is stored. @@ -1997,11 +2016,11 @@ public void edsResourceDeletedByCds() { verify(cdsWatcher).onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); assertThat(cdsUpdate.edsServiceName()).isEqualTo(null); - assertThat(cdsUpdate.lrsServerName()).isEqualTo(""); + assertThat(cdsUpdate.lrsServerInfo()).isEqualTo(lrsServerInfo); verify(cdsResourceWatcher).onChanged(cdsUpdateCaptor.capture()); cdsUpdate = cdsUpdateCaptor.getValue(); assertThat(cdsUpdate.edsServiceName()).isEqualTo(EDS_RESOURCE); - assertThat(cdsUpdate.lrsServerName()).isNull(); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); verifyResourceMetadataAcked(CDS, resource, clusters.get(0), VERSION_1, TIME_INCREMENT); verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusters.get(1), VERSION_1, TIME_INCREMENT); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); @@ -2325,8 +2344,9 @@ public void streamClosedAndRetryRestartsResourceInitialFetchTimerForUnresolvedRe @Test public void reportLoadStatsToServer() { + xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); String clusterName = "cluster-foo.googleapis.com"; - ClusterDropStats dropStats = xdsClient.addClusterDropStats(clusterName, null); + ClusterDropStats dropStats = xdsClient.addClusterDropStats(lrsServerInfo, clusterName, null); LrsRpcCall lrsCall = loadReportCalls.poll(); lrsCall.verifyNextReportClusters(Collections.emptyList()); // initial LRS request @@ -2429,10 +2449,15 @@ public void serverSideListenerResponseErrorHandling_badDownstreamTlsContext() { List listeners = ImmutableList.of(Any.pack(listener)); call.sendResponse(ResourceType.LDS, listeners, "0", "0000"); // The response NACKed with errors indicating indices of the failed resources. - call.verifyRequestNack(LDS, LISTENER_RESOURCE, "", "0000", NODE, ImmutableList.of( - "LDS response Listener \'grpc/server?xds.resource.listening_address=0.0.0.0:7000\' " - + "validation error: common-tls-context is required in downstream-tls-context")); - verifyNoInteractions(ldsResourceWatcher); + String errorMsg = "LDS response Listener \'grpc/server?xds.resource.listening_address=" + + "0.0.0.0:7000\' validation error: " + + "common-tls-context is required in downstream-tls-context"; + call.verifyRequestNack(LDS, LISTENER_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Status.class); + verify(ldsResourceWatcher).onError(captor.capture()); + Status errorStatus = captor.getValue(); + assertThat(errorStatus.getCode()).isEqualTo(Status.UNAVAILABLE.getCode()); + assertThat(errorStatus.getDescription()).isEqualTo(errorMsg); } @Test @@ -2453,11 +2478,16 @@ public void serverSideListenerResponseErrorHandling_badTransportSocketName() { List listeners = ImmutableList.of(Any.pack(listener)); call.sendResponse(ResourceType.LDS, listeners, "0", "0000"); // The response NACKed with errors indicating indices of the failed resources. + String errorMsg = "LDS response Listener \'grpc/server?xds.resource.listening_address=" + + "0.0.0.0:7000\' validation error: " + + "transport-socket with name envoy.transport_sockets.bad1 not supported."; call.verifyRequestNack(LDS, LISTENER_RESOURCE, "", "0000", NODE, ImmutableList.of( - "LDS response Listener \'grpc/server?xds.resource.listening_address=0.0.0.0:7000\' " - + "validation error: " - + "transport-socket with name envoy.transport_sockets.bad1 not supported.")); - verifyNoInteractions(ldsResourceWatcher); + errorMsg)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Status.class); + verify(ldsResourceWatcher).onError(captor.capture()); + Status errorStatus = captor.getValue(); + assertThat(errorStatus.getCode()).isEqualTo(Status.UNAVAILABLE.getCode()); + assertThat(errorStatus.getDescription()).isEqualTo(errorMsg); } private DiscoveryRpcCall startResourceWatcher( diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index dfcf101fcf59..582747aecb72 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -27,6 +27,7 @@ import io.grpc.ClientStreamTracer; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; +import io.grpc.InsecureChannelCredentials; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.CreateSubchannelArgs; import io.grpc.LoadBalancer.Helper; @@ -44,6 +45,7 @@ import io.grpc.internal.FakeClock; import io.grpc.internal.ObjectPool; import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; @@ -85,7 +87,8 @@ public class ClusterImplLoadBalancerTest { private static final String AUTHORITY = "api.google.com"; private static final String CLUSTER = "cluster-foo.googleapis.com"; private static final String EDS_SERVICE_NAME = "service.googleapis.com"; - private static final String LRS_SERVER_NAME = ""; + private static final ServerInfo LRS_SERVER_INFO = + ServerInfo.create("api.google.com", InsecureChannelCredentials.create(), true); private final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @Override @@ -150,7 +153,7 @@ public void handleResolvedAddresses_propagateToChildPolicy() { FakeLoadBalancerProvider weightedTargetProvider = new FakeLoadBalancerProvider(XdsLbPolicies.WEIGHTED_TARGET_POLICY_NAME); Object weightedTargetConfig = new Object(); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); @@ -177,7 +180,7 @@ public void nameResolutionError_afterChildPolicyInstantiated_propagateToDownstre FakeLoadBalancerProvider weightedTargetProvider = new FakeLoadBalancerProvider(XdsLbPolicies.WEIGHTED_TARGET_POLICY_NAME); Object weightedTargetConfig = new Object(); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); @@ -196,7 +199,7 @@ public void recordLoadStats() { LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); WeightedTargetConfig weightedTargetConfig = buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); @@ -247,7 +250,7 @@ public void dropRpcsWithRespectToLbConfigDropCategories() { LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); WeightedTargetConfig weightedTargetConfig = buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.singletonList(DropOverload.create("throttle", 500_000)), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); @@ -276,7 +279,7 @@ public void dropRpcsWithRespectToLbConfigDropCategories() { assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L); // Config update updates drop policies. - config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, null, + config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.singletonList(DropOverload.create("lb", 1_000_000)), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); loadBalancer.handleResolvedAddresses( @@ -323,7 +326,7 @@ private void subtest_maxConcurrentRequests_appliedByLbConfig(boolean enableCircu LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); WeightedTargetConfig weightedTargetConfig = buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, maxConcurrentRequests, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); @@ -365,7 +368,7 @@ private void subtest_maxConcurrentRequests_appliedByLbConfig(boolean enableCircu // Config update increments circuit breakers max_concurrent_requests threshold. maxConcurrentRequests = 101L; - config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, maxConcurrentRequests, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); deliverAddressesAndConfig(Collections.singletonList(endpoint), config); @@ -411,7 +414,7 @@ private void subtest_maxConcurrentRequests_appliedWithDefaultValue( LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); WeightedTargetConfig weightedTargetConfig = buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); @@ -457,7 +460,7 @@ public void endpointAddressesAttachedWithClusterName() { LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); WeightedTargetConfig weightedTargetConfig = buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); // One locality with two endpoints. @@ -498,7 +501,7 @@ private void subtest_endpointAddressesAttachedWithTlsConfig(boolean enableSecuri LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); WeightedTargetConfig weightedTargetConfig = buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); - ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), upstreamTlsContext); // One locality with two endpoints. @@ -525,7 +528,7 @@ private void subtest_endpointAddressesAttachedWithTlsConfig(boolean enableSecuri } // Removes UpstreamTlsContext from the config. - config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); deliverAddressesAndConfig(Arrays.asList(endpoint1, endpoint2), config); @@ -539,7 +542,7 @@ private void subtest_endpointAddressesAttachedWithTlsConfig(boolean enableSecuri // Config with a new UpstreamTlsContext. upstreamTlsContext = CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe1", true); - config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, + config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, null, Collections.emptyList(), new PolicySelection(weightedTargetProvider, weightedTargetConfig), upstreamTlsContext); deliverAddressesAndConfig(Arrays.asList(endpoint1, endpoint2), config); @@ -761,13 +764,15 @@ public Attributes getAttributes() { private final class FakeXdsClient extends XdsClient { @Override - ClusterDropStats addClusterDropStats(String clusterName, @Nullable String edsServiceName) { + ClusterDropStats addClusterDropStats( + ServerInfo lrsServerInfo, String clusterName, @Nullable String edsServiceName) { return loadStatsManager.getClusterDropStats(clusterName, edsServiceName); } @Override - ClusterLocalityStats addClusterLocalityStats(String clusterName, - @Nullable String edsServiceName, Locality locality) { + ClusterLocalityStats addClusterLocalityStats( + ServerInfo lrsServerInfo, String clusterName, @Nullable String edsServiceName, + Locality locality) { return loadStatsManager.getClusterLocalityStats(clusterName, edsServiceName, locality); } diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 26f1a2b3cf7e..ccc1975265d6 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -34,6 +34,7 @@ import io.grpc.ChannelLogger; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; +import io.grpc.InsecureChannelCredentials; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.PickResult; @@ -56,6 +57,7 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; @@ -103,7 +105,8 @@ public class ClusterResolverLoadBalancerTest { private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com"; private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com"; private static final String DNS_HOST_NAME = "dns-service.googleapis.com"; - private static final String LRS_SERVER_NAME = "lrs.googleapis.com"; + private static final ServerInfo LRS_SERVER_INFO = + ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create(), true); private final Locality locality1 = Locality.create("test-region-1", "test-zone-1", "test-subzone-1"); private final Locality locality2 = @@ -113,11 +116,11 @@ public class ClusterResolverLoadBalancerTest { private final UpstreamTlsContext tlsContext = CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true); private final DiscoveryMechanism edsDiscoveryMechanism1 = - DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_NAME, 100L, tlsContext); + DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext); private final DiscoveryMechanism edsDiscoveryMechanism2 = - DiscoveryMechanism.forEds(CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_NAME, 200L, tlsContext); + DiscoveryMechanism.forEds(CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext); private final DiscoveryMechanism logicalDnsDiscoveryMechanism = - DiscoveryMechanism.forLogicalDns(CLUSTER_DNS, DNS_HOST_NAME, LRS_SERVER_NAME, 300L, null); + DiscoveryMechanism.forLogicalDns(CLUSTER_DNS, DNS_HOST_NAME, LRS_SERVER_INFO, 300L, null); private final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @@ -256,7 +259,7 @@ public void edsClustersWithRingHashEndpointLbPolicy() { .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig = (ClusterImplConfig) priorityChildConfig.policySelection.getConfig(); - assertClusterImplConfig(clusterImplConfig, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_NAME, 100L, + assertClusterImplConfig(clusterImplConfig, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext, Collections.emptyList(), "ring_hash"); RingHashConfig ringHashConfig = (RingHashConfig) clusterImplConfig.childPolicy.getConfig(); @@ -320,7 +323,7 @@ public void onlyEdsClusters_receivedEndpoints() { .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig1 = (ClusterImplConfig) priorityChildConfig1.policySelection.getConfig(); - assertClusterImplConfig(clusterImplConfig1, CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_NAME, 200L, + assertClusterImplConfig(clusterImplConfig1, CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext, Collections.emptyList(), WEIGHTED_TARGET_POLICY_NAME); WeightedTargetConfig weightedTargetConfig1 = (WeightedTargetConfig) clusterImplConfig1.childPolicy.getConfig(); @@ -335,7 +338,7 @@ public void onlyEdsClusters_receivedEndpoints() { .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig2 = (ClusterImplConfig) priorityChildConfig2.policySelection.getConfig(); - assertClusterImplConfig(clusterImplConfig2, CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_NAME, 200L, + assertClusterImplConfig(clusterImplConfig2, CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext, Collections.emptyList(), WEIGHTED_TARGET_POLICY_NAME); WeightedTargetConfig weightedTargetConfig2 = (WeightedTargetConfig) clusterImplConfig2.childPolicy.getConfig(); @@ -358,7 +361,7 @@ public void onlyEdsClusters_receivedEndpoints() { .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig3 = (ClusterImplConfig) priorityChildConfig3.policySelection.getConfig(); - assertClusterImplConfig(clusterImplConfig3, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_NAME, 100L, + assertClusterImplConfig(clusterImplConfig3, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext, Collections.emptyList(), WEIGHTED_TARGET_POLICY_NAME); WeightedTargetConfig weightedTargetConfig3 = (WeightedTargetConfig) clusterImplConfig3.childPolicy.getConfig(); @@ -549,7 +552,7 @@ public void onlyLogicalDnsCluster_endpointsResolved() { .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig = (ClusterImplConfig) priorityChildConfig.policySelection.getConfig(); - assertClusterImplConfig(clusterImplConfig, CLUSTER_DNS, null, LRS_SERVER_NAME, 300L, null, + assertClusterImplConfig(clusterImplConfig, CLUSTER_DNS, null, LRS_SERVER_INFO, 300L, null, Collections.emptyList(), "pick_first"); assertAddressesEqual(Arrays.asList(endpoint1, endpoint2), childBalancer.addresses); } @@ -884,12 +887,12 @@ private static void assertPicker(SubchannelPicker picker, Status expectedStatus, } private static void assertClusterImplConfig(ClusterImplConfig config, String cluster, - @Nullable String edsServiceName, String lrsServerName, Long maxConcurrentRequests, + @Nullable String edsServiceName, ServerInfo lrsServerInfo, Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext, List dropCategories, String childPolicy) { assertThat(config.cluster).isEqualTo(cluster); assertThat(config.edsServiceName).isEqualTo(edsServiceName); - assertThat(config.lrsServerName).isEqualTo(lrsServerName); + assertThat(config.lrsServerInfo).isEqualTo(lrsServerInfo); assertThat(config.maxConcurrentRequests).isEqualTo(maxConcurrentRequests); assertThat(config.tlsContext).isEqualTo(tlsContext); assertThat(config.dropCategories).isEqualTo(dropCategories); diff --git a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java index 9a50e2e0599a..7c6abee28355 100644 --- a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java +++ b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java @@ -17,7 +17,6 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; import static io.grpc.xds.AbstractXdsClient.ResourceType.LDS; @@ -26,28 +25,17 @@ import com.google.common.collect.ImmutableMap; import com.google.protobuf.Any; -import com.google.protobuf.Timestamp; import io.envoyproxy.envoy.admin.v3.ClientResourceStatus; -import io.envoyproxy.envoy.admin.v3.ClustersConfigDump; -import io.envoyproxy.envoy.admin.v3.ClustersConfigDump.DynamicCluster; -import io.envoyproxy.envoy.admin.v3.EndpointsConfigDump; -import io.envoyproxy.envoy.admin.v3.EndpointsConfigDump.DynamicEndpointConfig; -import io.envoyproxy.envoy.admin.v3.ListenersConfigDump; -import io.envoyproxy.envoy.admin.v3.ListenersConfigDump.DynamicListener; -import io.envoyproxy.envoy.admin.v3.ListenersConfigDump.DynamicListenerState; -import io.envoyproxy.envoy.admin.v3.RoutesConfigDump; -import io.envoyproxy.envoy.admin.v3.RoutesConfigDump.DynamicRouteConfig; -import io.envoyproxy.envoy.admin.v3.UpdateFailureState; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.core.v3.Node; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; import io.envoyproxy.envoy.service.status.v3.ClientConfig; +import io.envoyproxy.envoy.service.status.v3.ClientConfig.GenericXdsConfig; import io.envoyproxy.envoy.service.status.v3.ClientStatusDiscoveryServiceGrpc; import io.envoyproxy.envoy.service.status.v3.ClientStatusRequest; import io.envoyproxy.envoy.service.status.v3.ClientStatusResponse; -import io.envoyproxy.envoy.service.status.v3.PerXdsConfig; import io.envoyproxy.envoy.type.matcher.v3.NodeMatcher; import io.grpc.InsecureChannelCredentials; import io.grpc.Status; @@ -92,11 +80,6 @@ Bootstrapper.BootstrapInfo getBootstrapInfo() { .build(); } - @Override - String getCurrentVersion(ResourceType type) { - return "getCurrentVersion." + type.name(); - } - @Override Map getSubscribedResourcesMetadata(ResourceType type) { return ImmutableMap.of(); @@ -266,23 +249,11 @@ public static class MetadataToProtoTests { private static final String RDS_RESOURCE = "route-configuration.googleapis.com"; private static final String CDS_RESOURCE = "cluster.googleapis.com"; private static final String EDS_RESOURCE = "cluster-load-assignment.googleapis.com"; - private static final String VERSION_ACK = "42"; - private static final String VERSION_NACK = "43"; - private static final String ERROR = "Parse error line 1\n Parse error line 2"; - - // Test timestamps. - private static final Timestamp TIMESTAMP_ZERO = Timestamp.getDefaultInstance(); + private static final String VERSION_ACK_LDS = "42"; + private static final String VERSION_ACK_RDS = "38"; + private static final String VERSION_ACK_CDS = "51"; + private static final String VERSION_ACK_EDS = "29"; private static final long NANOS_LAST_UPDATE = 1577923199_606042047L; - private static final Timestamp TIMESTAMP_LAST_UPDATE = Timestamp.newBuilder() - .setSeconds(1577923199L) // 2020-01-01T23:59:59Z - .setNanos(606042047) - .build(); - private static final long NANOS_FAILED_UPDATE = 1609545599_732105843L; - private static final Timestamp TIMESTAMP_FAILED_UPDATE = Timestamp.newBuilder() - .setSeconds(1609545599L) // 2021-01-01T23:59:59Z - .setNanos(732105843) - .build(); - // Raw resources. private static final Any RAW_LISTENER = Any.pack(Listener.newBuilder().setName(LDS_RESOURCE).build()); @@ -293,388 +264,15 @@ public static class MetadataToProtoTests { private static final Any RAW_CLUSTER_LOAD_ASSIGNMENT = Any.pack(ClusterLoadAssignment.newBuilder().setClusterName(EDS_RESOURCE).build()); - // Test metadata: no data received states. - private static final ResourceMetadata METADATA_UNKNOWN = - ResourceMetadata.newResourceMetadataUnknown(); - private static final ResourceMetadata METADATA_DOES_NOT_EXIST = - ResourceMetadata.newResourceMetadataDoesNotExist(); - private static final ResourceMetadata METADATA_REQUESTED = - ResourceMetadata.newResourceMetadataRequested(); - // Test metadata: resource acknowledged state, per resource type. private static final ResourceMetadata METADATA_ACKED_LDS = ResourceMetadata - .newResourceMetadataAcked(RAW_LISTENER, VERSION_ACK, NANOS_LAST_UPDATE); + .newResourceMetadataAcked(RAW_LISTENER, VERSION_ACK_LDS, NANOS_LAST_UPDATE); private static final ResourceMetadata METADATA_ACKED_RDS = ResourceMetadata - .newResourceMetadataAcked(RAW_ROUTE_CONFIGURATION, VERSION_ACK, NANOS_LAST_UPDATE); + .newResourceMetadataAcked(RAW_ROUTE_CONFIGURATION, VERSION_ACK_RDS, NANOS_LAST_UPDATE); private static final ResourceMetadata METADATA_ACKED_CDS = ResourceMetadata - .newResourceMetadataAcked(RAW_CLUSTER, VERSION_ACK, NANOS_LAST_UPDATE); + .newResourceMetadataAcked(RAW_CLUSTER, VERSION_ACK_CDS, NANOS_LAST_UPDATE); private static final ResourceMetadata METADATA_ACKED_EDS = ResourceMetadata - .newResourceMetadataAcked(RAW_CLUSTER_LOAD_ASSIGNMENT, VERSION_ACK, NANOS_LAST_UPDATE); - - // Test resources list. - private static final ImmutableMap RESOURCES_METADATA = - ImmutableMap.of("A", METADATA_UNKNOWN, "B", METADATA_REQUESTED); - - /* LDS tests */ - - @Test - public void dumpLdsConfig() { - ListenersConfigDump ldsConfig = CsdsService.dumpLdsConfig(RESOURCES_METADATA, VERSION_ACK); - assertThat(ldsConfig.getVersionInfo()).isEqualTo(VERSION_ACK); - assertThat(ldsConfig.getStaticListenersCount()).isEqualTo(0); - assertThat(ldsConfig.getDynamicListenersCount()).isEqualTo(2); - // Minimal check to confirm that resources generated from corresponding metadata. - DynamicListener listenerA = ldsConfig.getDynamicListeners(0); - assertThat(listenerA.getName()).isEqualTo("A"); - assertThat(listenerA.getClientStatus()).isEqualTo(ClientResourceStatus.UNKNOWN); - DynamicListener listenerB = ldsConfig.getDynamicListeners(1); - assertThat(listenerB.getName()).isEqualTo("B"); - assertThat(listenerB.getClientStatus()).isEqualTo(ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicListener_metadataUnknown() { - DynamicListener dynamicListener = - CsdsService.buildDynamicListener(LDS_RESOURCE, METADATA_UNKNOWN); - verifyDynamicListener(dynamicListener, ClientResourceStatus.UNKNOWN); - verifyDynamicListenerStateNoData(dynamicListener.getActiveState()); - } - - @Test - public void buildDynamicListener_metadataDoesNotExist() { - DynamicListener dynamicListener = - CsdsService.buildDynamicListener(LDS_RESOURCE, METADATA_DOES_NOT_EXIST); - verifyDynamicListener(dynamicListener, ClientResourceStatus.DOES_NOT_EXIST); - verifyDynamicListenerStateNoData(dynamicListener.getActiveState()); - } - - @Test - public void buildDynamicListener_metadataRequested() { - DynamicListener dynamicListener = - CsdsService.buildDynamicListener(LDS_RESOURCE, METADATA_REQUESTED); - verifyDynamicListener(dynamicListener, ClientResourceStatus.REQUESTED); - verifyDynamicListenerStateNoData(dynamicListener.getActiveState()); - } - - @Test - public void buildDynamicListener_metadataAcked() { - DynamicListener dynamicListener = - CsdsService.buildDynamicListener(LDS_RESOURCE, METADATA_ACKED_LDS); - verifyDynamicListener(dynamicListener, ClientResourceStatus.ACKED); - verifyDynamicListenerStateAccepted(dynamicListener.getActiveState()); - } - - @Test - public void buildDynamicListener_metadataNackedFromRequested() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_REQUESTED, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicListener dynamicListener = CsdsService.buildDynamicListener(LDS_RESOURCE, metadata); - verifyDynamicListener(dynamicListener, ClientResourceStatus.NACKED); - verifyErrorState(dynamicListener.getErrorState()); - verifyDynamicListenerStateNoData(dynamicListener.getActiveState()); - } - - @Test - public void buildDynamicListener_metadataNackedFromAcked() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_ACKED_LDS, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicListener dynamicListener = CsdsService.buildDynamicListener(LDS_RESOURCE, metadata); - verifyDynamicListener(dynamicListener, ClientResourceStatus.NACKED); - verifyErrorState(dynamicListener.getErrorState()); - verifyDynamicListenerStateAccepted(dynamicListener.getActiveState()); - } - - private void verifyDynamicListener( - DynamicListener dynamicListener, ClientResourceStatus status) { - assertWithMessage("name").that(dynamicListener.getName()).isEqualTo(LDS_RESOURCE); - assertWithMessage("active_state").that(dynamicListener.hasActiveState()).isTrue(); - assertWithMessage("warming_state").that(dynamicListener.hasWarmingState()).isFalse(); - assertWithMessage("draining_state").that(dynamicListener.hasDrainingState()).isFalse(); - assertWithMessage("error_state").that(dynamicListener.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicListener.getClientStatus()).isEqualTo(status); - } - - private void verifyDynamicListenerStateNoData(DynamicListenerState dynamicListenerState) { - assertWithMessage("version_info").that(dynamicListenerState.getVersionInfo()).isEmpty(); - assertWithMessage("listener").that(dynamicListenerState.hasListener()).isFalse(); - assertWithMessage("last_updated").that(dynamicListenerState.getLastUpdated()) - .isEqualTo(TIMESTAMP_ZERO); - } - - private void verifyDynamicListenerStateAccepted(DynamicListenerState dynamicListenerState) { - assertWithMessage("version_info").that(dynamicListenerState.getVersionInfo()) - .isEqualTo(VERSION_ACK); - assertWithMessage("listener").that(dynamicListenerState.hasListener()).isTrue(); - assertWithMessage("listener").that(dynamicListenerState.getListener()) - .isEqualTo(RAW_LISTENER); - assertWithMessage("last_updated").that(dynamicListenerState.getLastUpdated()) - .isEqualTo(TIMESTAMP_LAST_UPDATE); - } - - /* RDS tests */ - - @Test - public void dumpRdsConfig() { - RoutesConfigDump rdsConfig = CsdsService.dumpRdsConfig(RESOURCES_METADATA); - assertThat(rdsConfig.getStaticRouteConfigsCount()).isEqualTo(0); - assertThat(rdsConfig.getDynamicRouteConfigsCount()).isEqualTo(2); - // Minimal check to confirm that resources generated from corresponding metadata. - assertThat(rdsConfig.getDynamicRouteConfigs(0).getClientStatus()) - .isEqualTo(ClientResourceStatus.UNKNOWN); - assertThat(rdsConfig.getDynamicRouteConfigs(1).getClientStatus()) - .isEqualTo(ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicRouteConfig_metadataUnknown() { - verifyDynamicRouteConfigNoData( - CsdsService.buildDynamicRouteConfig(METADATA_UNKNOWN), - ClientResourceStatus.UNKNOWN); - } - - @Test - public void buildDynamicRouteConfig_metadataDoesNotExist() { - verifyDynamicRouteConfigNoData( - CsdsService.buildDynamicRouteConfig(METADATA_DOES_NOT_EXIST), - ClientResourceStatus.DOES_NOT_EXIST); - } - - @Test - public void buildDynamicRouteConfig_metadataRequested() { - verifyDynamicRouteConfigNoData( - CsdsService.buildDynamicRouteConfig(METADATA_REQUESTED), - ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicRouteConfig_metadataAcked() { - verifyDynamicRouteConfigAccepted( - CsdsService.buildDynamicRouteConfig(METADATA_ACKED_RDS), - ClientResourceStatus.ACKED); - } - - @Test - public void buildDynamicRouteConfig_metadataNackedFromRequested() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_REQUESTED, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicRouteConfig dynamicRouteConfig = CsdsService.buildDynamicRouteConfig(metadata); - verifyDynamicRouteConfigNoData(dynamicRouteConfig, ClientResourceStatus.NACKED); - verifyErrorState(dynamicRouteConfig.getErrorState()); - } - - @Test - public void buildDynamicRouteConfig_metadataNackedFromAcked() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_ACKED_RDS, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicRouteConfig dynamicRouteConfig = CsdsService.buildDynamicRouteConfig(metadata); - verifyDynamicRouteConfigAccepted(dynamicRouteConfig, ClientResourceStatus.NACKED); - verifyErrorState(dynamicRouteConfig.getErrorState()); - } - - private void verifyDynamicRouteConfigNoData( - DynamicRouteConfig dynamicRouteConfig, ClientResourceStatus status) { - assertWithMessage("version_info").that(dynamicRouteConfig.getVersionInfo()).isEmpty(); - assertWithMessage("route_config").that(dynamicRouteConfig.hasRouteConfig()).isFalse(); - assertWithMessage("last_updated").that(dynamicRouteConfig.getLastUpdated()) - .isEqualTo(TIMESTAMP_ZERO); - assertWithMessage("error_state").that(dynamicRouteConfig.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicRouteConfig.getClientStatus()) - .isEqualTo(status); - } - - private void verifyDynamicRouteConfigAccepted( - DynamicRouteConfig dynamicRouteConfig, ClientResourceStatus status) { - assertWithMessage("version_info").that(dynamicRouteConfig.getVersionInfo()) - .isEqualTo(VERSION_ACK); - assertWithMessage("route_config").that(dynamicRouteConfig.hasRouteConfig()).isTrue(); - assertWithMessage("route_config").that(dynamicRouteConfig.getRouteConfig()) - .isEqualTo(RAW_ROUTE_CONFIGURATION); - assertWithMessage("last_updated").that(dynamicRouteConfig.getLastUpdated()) - .isEqualTo(TIMESTAMP_LAST_UPDATE); - assertWithMessage("error_state").that(dynamicRouteConfig.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicRouteConfig.getClientStatus()) - .isEqualTo(status); - } - - /* CDS tests */ - - @Test - public void dumpCdsConfig() { - ClustersConfigDump cdsConfig = CsdsService.dumpCdsConfig(RESOURCES_METADATA, VERSION_ACK); - assertThat(cdsConfig.getVersionInfo()).isEqualTo(VERSION_ACK); - assertThat(cdsConfig.getStaticClustersCount()).isEqualTo(0); - assertThat(cdsConfig.getDynamicWarmingClustersCount()).isEqualTo(0); - assertThat(cdsConfig.getDynamicActiveClustersCount()).isEqualTo(2); - // Minimal check to confirm that resources generated from corresponding metadata. - assertThat(cdsConfig.getDynamicActiveClusters(0).getClientStatus()) - .isEqualTo(ClientResourceStatus.UNKNOWN); - assertThat(cdsConfig.getDynamicActiveClusters(1).getClientStatus()) - .isEqualTo(ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicCluster_metadataUnknown() { - verifyDynamicClusterNoData( - CsdsService.buildDynamicCluster(METADATA_UNKNOWN), - ClientResourceStatus.UNKNOWN); - } - - @Test - public void buildDynamicCluster_metadataDoesNotExist() { - verifyDynamicClusterNoData( - CsdsService.buildDynamicCluster(METADATA_DOES_NOT_EXIST), - ClientResourceStatus.DOES_NOT_EXIST); - } - - @Test - public void buildDynamicCluster_metadataRequested() { - verifyDynamicClusterNoData( - CsdsService.buildDynamicCluster(METADATA_REQUESTED), - ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicCluster_metadataAcked() { - verifyDynamicClusterAccepted( - CsdsService.buildDynamicCluster(METADATA_ACKED_CDS), - ClientResourceStatus.ACKED); - } - - @Test - public void buildDynamicCluster_metadataNackedFromRequested() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_REQUESTED, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicCluster dynamicCluster = CsdsService.buildDynamicCluster(metadata); - verifyDynamicClusterNoData(dynamicCluster, ClientResourceStatus.NACKED); - verifyErrorState(dynamicCluster.getErrorState()); - } - - @Test - public void buildDynamicCluster_metadataNackedFromAcked() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_ACKED_CDS, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicCluster dynamicCluster = CsdsService.buildDynamicCluster(metadata); - verifyDynamicClusterAccepted(dynamicCluster, ClientResourceStatus.NACKED); - verifyErrorState(dynamicCluster.getErrorState()); - } - - private void verifyDynamicClusterNoData( - DynamicCluster dynamicCluster, ClientResourceStatus status) { - assertWithMessage("version_info").that(dynamicCluster.getVersionInfo()).isEmpty(); - assertWithMessage("route_config").that(dynamicCluster.hasCluster()).isFalse(); - assertWithMessage("last_updated").that(dynamicCluster.getLastUpdated()) - .isEqualTo(TIMESTAMP_ZERO); - assertWithMessage("error_state").that(dynamicCluster.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicCluster.getClientStatus()).isEqualTo(status); - } - - private void verifyDynamicClusterAccepted( - DynamicCluster dynamicCluster, ClientResourceStatus status) { - assertWithMessage("version_info").that(dynamicCluster.getVersionInfo()) - .isEqualTo(VERSION_ACK); - assertWithMessage("route_config").that(dynamicCluster.hasCluster()).isTrue(); - assertWithMessage("route_config").that(dynamicCluster.getCluster()).isEqualTo(RAW_CLUSTER); - assertWithMessage("last_updated").that(dynamicCluster.getLastUpdated()) - .isEqualTo(TIMESTAMP_LAST_UPDATE); - assertWithMessage("error_state").that(dynamicCluster.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicCluster.getClientStatus()).isEqualTo(status); - } - - /* EDS tests */ - - @Test - public void dumpEdsConfig() { - EndpointsConfigDump edsConfig = CsdsService.dumpEdsConfig(RESOURCES_METADATA); - assertThat(edsConfig.getStaticEndpointConfigsCount()).isEqualTo(0); - assertThat(edsConfig.getDynamicEndpointConfigsCount()).isEqualTo(2); - // Minimal check to confirm that resources generated from corresponding metadata. - assertThat(edsConfig.getDynamicEndpointConfigs(0).getClientStatus()) - .isEqualTo(ClientResourceStatus.UNKNOWN); - assertThat(edsConfig.getDynamicEndpointConfigs(1).getClientStatus()) - .isEqualTo(ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicEndpointConfig_metadataUnknown() { - buildDynamicEndpointConfigNoData( - CsdsService.buildDynamicEndpointConfig(METADATA_UNKNOWN), - ClientResourceStatus.UNKNOWN); - } - - @Test - public void buildDynamicEndpointConfig_metadataDoesNotExist() { - buildDynamicEndpointConfigNoData( - CsdsService.buildDynamicEndpointConfig(METADATA_DOES_NOT_EXIST), - ClientResourceStatus.DOES_NOT_EXIST); - } - - @Test - public void buildDynamicEndpointConfig_metadataRequested() { - buildDynamicEndpointConfigNoData( - CsdsService.buildDynamicEndpointConfig(METADATA_REQUESTED), - ClientResourceStatus.REQUESTED); - } - - @Test - public void buildDynamicEndpointConfig_metadataAcked() { - verifyDynamicEndpointConfigAccepted( - CsdsService.buildDynamicEndpointConfig(METADATA_ACKED_EDS), - ClientResourceStatus.ACKED); - } - - @Test - public void buildDynamicEndpointConfig_metadataNackedFromRequested() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_REQUESTED, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicEndpointConfig dynamicEndpointConfig = - CsdsService.buildDynamicEndpointConfig(metadata); - buildDynamicEndpointConfigNoData(dynamicEndpointConfig, ClientResourceStatus.NACKED); - verifyErrorState(dynamicEndpointConfig.getErrorState()); - } - - @Test - public void buildDynamicEndpointConfig_metadataNackedFromAcked() { - ResourceMetadata metadata = ResourceMetadata.newResourceMetadataNacked( - METADATA_ACKED_EDS, VERSION_NACK, NANOS_FAILED_UPDATE, ERROR); - DynamicEndpointConfig dynamicEndpointConfig = - CsdsService.buildDynamicEndpointConfig(metadata); - verifyDynamicEndpointConfigAccepted(dynamicEndpointConfig, ClientResourceStatus.NACKED); - verifyErrorState(dynamicEndpointConfig.getErrorState()); - } - - private void buildDynamicEndpointConfigNoData( - DynamicEndpointConfig dynamicEndpointConfig, ClientResourceStatus status) { - assertWithMessage("version_info").that(dynamicEndpointConfig.getVersionInfo()).isEmpty(); - assertWithMessage("route_config").that(dynamicEndpointConfig.hasEndpointConfig()).isFalse(); - assertWithMessage("last_updated").that(dynamicEndpointConfig.getLastUpdated()) - .isEqualTo(TIMESTAMP_ZERO); - assertWithMessage("error_state").that(dynamicEndpointConfig.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicEndpointConfig.getClientStatus()) - .isEqualTo(status); - } - - private void verifyDynamicEndpointConfigAccepted( - DynamicEndpointConfig dynamicEndpointConfig, ClientResourceStatus status) { - assertWithMessage("version_info").that(dynamicEndpointConfig.getVersionInfo()) - .isEqualTo(VERSION_ACK); - assertWithMessage("route_config").that(dynamicEndpointConfig.hasEndpointConfig()).isTrue(); - assertWithMessage("route_config").that(dynamicEndpointConfig.getEndpointConfig()) - .isEqualTo(RAW_CLUSTER_LOAD_ASSIGNMENT); - assertWithMessage("last_updated").that(dynamicEndpointConfig.getLastUpdated()) - .isEqualTo(TIMESTAMP_LAST_UPDATE); - assertWithMessage("error_state").that(dynamicEndpointConfig.hasErrorState()) - .isEqualTo(status.equals(ClientResourceStatus.NACKED)); - assertWithMessage("client_status").that(dynamicEndpointConfig.getClientStatus()) - .isEqualTo(status); - } - - /* Common methods. */ + .newResourceMetadataAcked(RAW_CLUSTER_LOAD_ASSIGNMENT, VERSION_ACK_EDS, NANOS_LAST_UPDATE); @Test public void metadataStatusToClientStatus() { @@ -691,7 +289,7 @@ public void metadataStatusToClientStatus() { } @Test - public void getClientConfigForXdsClient_subscribedResourcesToPerXdsConfig() { + public void getClientConfigForXdsClient_subscribedResourcesToGenericXdsConfig() { ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(new XdsClient() { @Override Bootstrapper.BootstrapInfo getBootstrapInfo() { @@ -703,11 +301,6 @@ Bootstrapper.BootstrapInfo getBootstrapInfo() { .build(); } - @Override - String getCurrentVersion(ResourceType type) { - return "getCurrentVersion." + type.name(); - } - @Override Map getSubscribedResourcesMetadata(ResourceType type) { switch (type) { @@ -730,58 +323,35 @@ Map getSubscribedResourcesMetadata(ResourceType type) // Minimal verification to confirm that the data/metadata XdsClient provides, // is propagated to the correct resource types. - @SuppressWarnings("deprecation") - int xdsConfigCount = clientConfig.getXdsConfigCount(); + int xdsConfigCount = clientConfig.getGenericXdsConfigsCount(); assertThat(xdsConfigCount).isEqualTo(4); - EnumMap configDumps = mapConfigDumps(clientConfig); + EnumMap configDumps = mapConfigDumps(clientConfig); assertThat(configDumps.keySet()).containsExactly(LDS, RDS, CDS, EDS); // LDS. - // Both the version provided by XdsClient.getCurrentVersion(), - // and the resource name provided by XdsClient.getSubscribedResourcesMetadata() are used. - PerXdsConfig perXdsConfigLds = configDumps.get(LDS); - verifyPerXdsConfigEmptyFields(perXdsConfigLds); - ListenersConfigDump listenerConfig = perXdsConfigLds.getListenerConfig(); - assertThat(listenerConfig.getVersionInfo()).isEqualTo("getCurrentVersion.LDS"); - assertThat(listenerConfig.getDynamicListenersCount()).isEqualTo(1); - DynamicListener dynamicListener = listenerConfig.getDynamicListeners(0); - assertThat(dynamicListener.getName()).isEqualTo("subscribedResourceName.LDS"); - assertThat(dynamicListener.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); - assertThat(dynamicListener.getActiveState().getVersionInfo()).isEqualTo(VERSION_ACK); + GenericXdsConfig genericXdsConfigLds = configDumps.get(LDS); + assertThat(genericXdsConfigLds.getName()).isEqualTo("subscribedResourceName.LDS"); + assertThat(genericXdsConfigLds.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); + assertThat(genericXdsConfigLds.getVersionInfo()).isEqualTo(VERSION_ACK_LDS); + assertThat(genericXdsConfigLds.getXdsConfig()).isEqualTo(RAW_LISTENER); // RDS. - // Neither the version provided by XdsClient.getCurrentVersion(), - // nor the resource name provided by XdsClient.getSubscribedResourcesMetadata() are used. - PerXdsConfig perXdsConfigRds = configDumps.get(RDS); - verifyPerXdsConfigEmptyFields(perXdsConfigRds); - RoutesConfigDump routeConfig = perXdsConfigRds.getRouteConfig(); - assertThat(routeConfig.getDynamicRouteConfigsCount()).isEqualTo(1); - DynamicRouteConfig dynamicRouteConfig = routeConfig.getDynamicRouteConfigs(0); - assertThat(dynamicRouteConfig.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); - assertThat(dynamicRouteConfig.getVersionInfo()).isEqualTo(VERSION_ACK); + GenericXdsConfig genericXdsConfigRds = configDumps.get(RDS); + assertThat(genericXdsConfigRds.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); + assertThat(genericXdsConfigRds.getVersionInfo()).isEqualTo(VERSION_ACK_RDS); + assertThat(genericXdsConfigRds.getXdsConfig()).isEqualTo(RAW_ROUTE_CONFIGURATION); // CDS. - // Only the version provided by XdsClient.getCurrentVersion() is used, - // the resource name provided by XdsClient.getSubscribedResourcesMetadata() is ignored. - PerXdsConfig perXdsConfigCds = configDumps.get(CDS); - verifyPerXdsConfigEmptyFields(perXdsConfigRds); - ClustersConfigDump clusterConfig = perXdsConfigCds.getClusterConfig(); - assertThat(clusterConfig.getVersionInfo()).isEqualTo("getCurrentVersion.CDS"); - assertThat(clusterConfig.getDynamicActiveClustersCount()).isEqualTo(1); - DynamicCluster dynamicCluster = clusterConfig.getDynamicActiveClusters(0); - assertThat(dynamicCluster.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); - assertThat(dynamicCluster.getVersionInfo()).isEqualTo(VERSION_ACK); + GenericXdsConfig genericXdsConfigCds = configDumps.get(CDS); + assertThat(genericXdsConfigCds.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); + assertThat(genericXdsConfigCds.getVersionInfo()).isEqualTo(VERSION_ACK_CDS); + assertThat(genericXdsConfigCds.getXdsConfig()).isEqualTo(RAW_CLUSTER); // RDS. - // Neither the version provided by XdsClient.getCurrentVersion(), - // nor the resource name provided by XdsClient.getSubscribedResourcesMetadata() are used. - PerXdsConfig perXdsConfigEds = configDumps.get(EDS); - verifyPerXdsConfigEmptyFields(perXdsConfigEds); - EndpointsConfigDump endpointConfig = perXdsConfigEds.getEndpointConfig(); - assertThat(endpointConfig.getDynamicEndpointConfigsCount()).isEqualTo(1); - DynamicEndpointConfig dynamicEndpointConfig = endpointConfig.getDynamicEndpointConfigs(0); - assertThat(dynamicEndpointConfig.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); - assertThat(dynamicEndpointConfig.getVersionInfo()).isEqualTo(VERSION_ACK); + GenericXdsConfig genericXdsConfigEds = configDumps.get(EDS); + assertThat(genericXdsConfigEds.getClientStatus()).isEqualTo(ClientResourceStatus.ACKED); + assertThat(genericXdsConfigEds.getVersionInfo()).isEqualTo(VERSION_ACK_EDS); + assertThat(genericXdsConfigEds.getXdsConfig()).isEqualTo(RAW_CLUSTER_LOAD_ASSIGNMENT); } @Test @@ -790,15 +360,6 @@ public void getClientConfigForXdsClient_noSubscribedResources() { verifyClientConfigNode(clientConfig); verifyClientConfigNoResources(clientConfig); } - - private void verifyErrorState(UpdateFailureState errorState) { - // failed_configuration currently not supported. - assertWithMessage("failed_configuration").that(errorState.hasFailedConfiguration()).isFalse(); - assertWithMessage("last_update_attempt").that(errorState.getLastUpdateAttempt()) - .isEqualTo(TIMESTAMP_FAILED_UPDATE); - assertWithMessage("details").that(errorState.getDetails()).isEqualTo(ERROR); - assertWithMessage("version_info").that(errorState.getVersionInfo()).isEqualTo(VERSION_NACK); - } } /** @@ -806,26 +367,10 @@ private void verifyErrorState(UpdateFailureState errorState) { * config dumps correctly, perform a minimal verification of the general shape of ClientConfig. */ private static void verifyClientConfigNoResources(ClientConfig clientConfig) { - // Expect PerXdsConfig for all resource types to be present, but empty. - @SuppressWarnings("deprecation") - int xdsConfigCount = clientConfig.getXdsConfigCount(); - assertThat(xdsConfigCount).isEqualTo(4); - EnumMap configDumps = mapConfigDumps(clientConfig); - assertThat(configDumps.keySet()).containsExactly(LDS, RDS, CDS, EDS); - - ListenersConfigDump listenerConfig = configDumps.get(LDS).getListenerConfig(); - assertThat(listenerConfig.getVersionInfo()).isEqualTo("getCurrentVersion.LDS"); - assertThat(listenerConfig.getDynamicListenersCount()).isEqualTo(0); - - RoutesConfigDump routeConfig = configDumps.get(RDS).getRouteConfig(); - assertThat(routeConfig.getDynamicRouteConfigsCount()).isEqualTo(0); - - ClustersConfigDump clusterConfig = configDumps.get(CDS).getClusterConfig(); - assertThat(clusterConfig.getVersionInfo()).isEqualTo("getCurrentVersion.CDS"); - assertThat(clusterConfig.getDynamicActiveClustersCount()).isEqualTo(0); - - EndpointsConfigDump endpointConfig = configDumps.get(EDS).getEndpointConfig(); - assertThat(endpointConfig.getDynamicEndpointConfigsCount()).isEqualTo(0); + int xdsConfigCount = clientConfig.getGenericXdsConfigsCount(); + assertThat(xdsConfigCount).isEqualTo(0); + EnumMap configDumps = mapConfigDumps(clientConfig); + assertThat(configDumps).isEmpty(); } /** @@ -838,42 +383,18 @@ private static void verifyClientConfigNode(ClientConfig clientConfig) { assertThat(node).isEqualTo(BOOTSTRAP_NODE.toEnvoyProtoNode()); } - /** Verify PerXdsConfig fields that are expected to be omitted. */ - private static void verifyPerXdsConfigEmptyFields(PerXdsConfig perXdsConfig) { - assertThat(perXdsConfig.getStatusValue()).isEqualTo(0); - @SuppressWarnings("deprecation") - int clientStatusValue = perXdsConfig.getClientStatusValue(); - assertThat(clientStatusValue).isEqualTo(0); - } - - private static EnumMap mapConfigDumps(ClientConfig config) { - EnumMap xdsConfigMap = new EnumMap<>(ResourceType.class); - @SuppressWarnings("deprecation") - List xdsConfigList = config.getXdsConfigList(); - for (PerXdsConfig perXdsConfig : xdsConfigList) { - ResourceType type = perXdsConfigToResourceType(perXdsConfig); + private static EnumMap mapConfigDumps(ClientConfig config) { + EnumMap xdsConfigMap = new EnumMap<>(ResourceType.class); + List xdsConfigList = config.getGenericXdsConfigsList(); + for (GenericXdsConfig genericXdsConfig : xdsConfigList) { + ResourceType type = ResourceType.fromTypeUrl(genericXdsConfig.getTypeUrl()); assertThat(type).isNotEqualTo(ResourceType.UNKNOWN); assertThat(xdsConfigMap).doesNotContainKey(type); - xdsConfigMap.put(type, perXdsConfig); + xdsConfigMap.put(type, genericXdsConfig); } return xdsConfigMap; } - private static ResourceType perXdsConfigToResourceType(PerXdsConfig perXdsConfig) { - switch (perXdsConfig.getPerXdsConfigCase()) { - case LISTENER_CONFIG: - return LDS; - case CLUSTER_CONFIG: - return CDS; - case ROUTE_CONFIG: - return RDS; - case ENDPOINT_CONFIG: - return EDS; - default: - return ResourceType.UNKNOWN; - } - } - private static class FakeXdsClientPoolFactory implements XdsClientPoolFactory { @Nullable private final XdsClient xdsClient; diff --git a/xds/src/test/java/io/grpc/xds/OrcaMetricReportingServerInterceptorTest.java b/xds/src/test/java/io/grpc/xds/OrcaMetricReportingServerInterceptorTest.java index b59871f5437c..4a2074b91d89 100644 --- a/xds/src/test/java/io/grpc/xds/OrcaMetricReportingServerInterceptorTest.java +++ b/xds/src/test/java/io/grpc/xds/OrcaMetricReportingServerInterceptorTest.java @@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat; -import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; +import com.github.xds.data.orca.v3.OrcaLoadReport; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; diff --git a/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java b/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java index b3cf80ee6171..5f5bc5a69aaa 100644 --- a/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java +++ b/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java @@ -34,9 +34,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; -import com.github.udpa.udpa.service.orca.v1.OpenRcaServiceGrpc; -import com.github.udpa.udpa.service.orca.v1.OrcaLoadReportRequest; +import com.github.xds.data.orca.v3.OrcaLoadReport; +import com.github.xds.service.orca.v3.OpenRcaServiceGrpc; +import com.github.xds.service.orca.v3.OrcaLoadReportRequest; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.util.Durations; import io.grpc.Attributes; diff --git a/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java b/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java index bee68c9e6343..a6e7c6aca20d 100644 --- a/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java +++ b/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java @@ -27,7 +27,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; +import com.github.xds.data.orca.v3.OrcaLoadReport; import io.grpc.ClientStreamTracer; import io.grpc.Metadata; import io.grpc.xds.OrcaPerRequestUtil.OrcaPerRequestReportListener; diff --git a/xds/src/test/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPluginTest.java b/xds/src/test/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPluginTest.java index 1bfe4a26f242..f8d1c2ef7c0a 100644 --- a/xds/src/test/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPluginTest.java +++ b/xds/src/test/java/io/grpc/xds/RouteLookupServiceClusterSpecifierPluginTest.java @@ -26,8 +26,8 @@ import io.grpc.lookup.v1.GrpcKeyBuilder.ExtraKeys; import io.grpc.lookup.v1.GrpcKeyBuilder.Name; import io.grpc.lookup.v1.NameMatcher; +import io.grpc.lookup.v1.RouteLookupClusterSpecifier; import io.grpc.lookup.v1.RouteLookupConfig; -import io.grpc.rls.RlsProtoData; import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,29 +57,38 @@ public void parseConfigWithAllFieldsGiven() { .addValidTargets("valid-target") .setDefaultTarget("default-target") .build(); + RouteLookupClusterSpecifier specifier = + RouteLookupClusterSpecifier.newBuilder().setRouteLookupConfig(routeLookupConfig).build(); RlsPluginConfig config = - RouteLookupServiceClusterSpecifierPlugin.INSTANCE.parsePlugin(Any.pack(routeLookupConfig)) + RouteLookupServiceClusterSpecifierPlugin.INSTANCE.parsePlugin(Any.pack(specifier)) .config; assertThat(config.typeUrl()).isEqualTo("type.googleapis.com/grpc.lookup.v1.RouteLookupConfig"); assertThat(config.config()).isEqualTo( - new RlsProtoData.RouteLookupConfig( - ImmutableList.of( - new RlsProtoData.GrpcKeyBuilder( + ImmutableMap.builder() + .put( + "grpcKeybuilders", + ImmutableList.of(ImmutableMap.of( + "names", ImmutableList.of( - new RlsProtoData.GrpcKeyBuilder.Name("service1", "method1"), - new RlsProtoData.GrpcKeyBuilder.Name("service2", "method2")), + ImmutableMap.of("service", "service1", "method", "method1"), + ImmutableMap.of("service", "service2", "method", "method2")), + "headers", ImmutableList.of( - new RlsProtoData.NameMatcher("key1", ImmutableList.of("v1"), true)), - RlsProtoData.ExtraKeys.create("host1", "service1", "method1"), - ImmutableMap.of("key2", "value2") - )), - "rls-cbt.googleapis.com", - 1234, - 56789L, - 1000L, - 5000, - ImmutableList.of("valid-target"), - "default-target")); + ImmutableMap.of( + "key", "key1", "names", ImmutableList.of("v1"), + "requiredMatch", true)), + "extraKeys", + ImmutableMap.of("host", "host1", "service", "service1", "method", "method1"), + "constantKeys", + ImmutableMap.of("key2", "value2")))) + .put("lookupService", "rls-cbt.googleapis.com") + .put("lookupServiceTimeout", "1.234s") + .put("maxAge", "56.789s") + .put("staleAge", "1s") + .put("cacheSizeBytes", "5000") + .put("validTargets", ImmutableList.of("valid-target")) + .put("defaultTarget","default-target") + .build()); } @Test @@ -96,47 +105,30 @@ public void parseConfigWithOptionalFieldsUnspecified() { .setCacheSizeBytes(5000) .addValidTargets("valid-target") .build(); + RouteLookupClusterSpecifier specifier = + RouteLookupClusterSpecifier.newBuilder().setRouteLookupConfig(routeLookupConfig).build(); RlsPluginConfig config = - RouteLookupServiceClusterSpecifierPlugin.INSTANCE.parsePlugin(Any.pack(routeLookupConfig)) + RouteLookupServiceClusterSpecifierPlugin.INSTANCE.parsePlugin(Any.pack(specifier)) .config; assertThat(config.typeUrl()).isEqualTo("type.googleapis.com/grpc.lookup.v1.RouteLookupConfig"); assertThat(config.config()).isEqualTo( - new RlsProtoData.RouteLookupConfig( - ImmutableList.of( - new RlsProtoData.GrpcKeyBuilder( + ImmutableMap.builder() + .put( + "grpcKeybuilders", + ImmutableList.of(ImmutableMap.of( + "names", ImmutableList.of( - new RlsProtoData.GrpcKeyBuilder.Name("service1"), - new RlsProtoData.GrpcKeyBuilder.Name("service2")), + ImmutableMap.of("service", "service1"), + ImmutableMap.of("service", "service2")), + "headers", ImmutableList.of( - new RlsProtoData.NameMatcher("key1", ImmutableList.of("v1"), true)), - RlsProtoData.ExtraKeys.create(null, null, null), - ImmutableMap.of() - )), - "rls-cbt.googleapis.com", - 1234, - null, - null, - 5000, - ImmutableList.of("valid-target"), - null)); - } - - @Test - public void parseInvalidConfig() { - RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() - .addGrpcKeybuilders( - GrpcKeyBuilder.newBuilder() - .addNames(Name.newBuilder().setService("service1")) - .addNames(Name.newBuilder().setService("service2")) - .addHeaders( - NameMatcher.newBuilder().setKey("key1").addNames("v1").setRequiredMatch(true))) - .setLookupService("rls-cbt.googleapis.com") - .setLookupServiceTimeout(Durations.fromMillis(1234)) - .setCacheSizeBytes(-5000) // negative - .addValidTargets("valid-target") - .build(); - ConfigOrError configOrError = - RouteLookupServiceClusterSpecifierPlugin.INSTANCE.parsePlugin(Any.pack(routeLookupConfig)); - assertThat(configOrError.errorDetail).contains("cacheSize must be positive"); + ImmutableMap.of( + "key", "key1", "names", ImmutableList.of("v1"), + "requiredMatch", true))))) + .put("lookupService", "rls-cbt.googleapis.com") + .put("lookupServiceTimeout", "1.234s") + .put("cacheSizeBytes", "5000") + .put("validTargets", ImmutableList.of("valid-target")) + .build()); } } diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 6a3cba4ac351..14a8f1ce7438 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.when; import io.grpc.InsecureChannelCredentials; -import io.grpc.ManagedChannel; import io.grpc.internal.ObjectPool; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.ServerInfo; @@ -90,7 +89,6 @@ public void refCountedXdsClientObjectPool_delayedCreation() { BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); RefCountedXdsClientObjectPool xdsClientPool = new RefCountedXdsClientObjectPool(bootstrapInfo); assertThat(xdsClientPool.getXdsClientForTest()).isNull(); - assertThat(xdsClientPool.getChannelForTest()).isNull(); XdsClient xdsClient = xdsClientPool.getObject(); assertThat(xdsClientPool.getXdsClientForTest()).isNotNull(); xdsClientPool.returnObject(xdsClient); @@ -113,7 +111,6 @@ public void refCountedXdsClientObjectPool_refCounted() { // returnObject twice assertThat(xdsClientPool.returnObject(xdsClient)).isNull(); assertThat(xdsClient.isShutDown()).isTrue(); - assertThat(xdsClientPool.getChannelForTest().isShutdown()).isTrue(); } @Test @@ -123,14 +120,11 @@ public void refCountedXdsClientObjectPool_getObjectCreatesNewInstanceIfAlreadySh BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); RefCountedXdsClientObjectPool xdsClientPool = new RefCountedXdsClientObjectPool(bootstrapInfo); XdsClient xdsClient1 = xdsClientPool.getObject(); - ManagedChannel channel1 = xdsClientPool.getChannelForTest(); assertThat(xdsClientPool.returnObject(xdsClient1)).isNull(); assertThat(xdsClient1.isShutDown()).isTrue(); - assertThat(channel1.isShutdown()).isTrue(); XdsClient xdsClient2 = xdsClientPool.getObject(); assertThat(xdsClient2).isNotSameInstanceAs(xdsClient1); - assertThat(xdsClientPool.getChannelForTest()).isNotSameInstanceAs(channel1); xdsClientPool.returnObject(xdsClient2); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index 1871cb79770e..a39a5495c097 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java @@ -63,15 +63,15 @@ import io.netty.handler.codec.http2.Http2ConnectionDecoder; import io.netty.handler.codec.http2.Http2ConnectionEncoder; import io.netty.handler.codec.http2.Http2Settings; -import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Arrays; import java.util.Collections; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -190,8 +190,8 @@ public void run() { try { start.get(5, TimeUnit.SECONDS); fail("Start should throw exception"); - } catch (ExecutionException ex) { - assertThat(ex.getCause()).isInstanceOf(IOException.class); + } catch (TimeoutException ex) { + assertThat(start.isDone()).isFalse(); } assertThat(selectorManager.getSelectorToUpdateSelector()).isSameInstanceAs(NO_FILTER_CHAIN); } @@ -214,8 +214,8 @@ public void run() { try { start.get(5, TimeUnit.SECONDS); fail("Start should throw exception"); - } catch (ExecutionException ex) { - assertThat(ex.getCause()).isInstanceOf(IOException.class); + } catch (TimeoutException ex) { + assertThat(start.isDone()).isFalse(); } assertThat(selectorManager.getSelectorToUpdateSelector()).isSameInstanceAs(NO_FILTER_CHAIN); } @@ -238,8 +238,8 @@ public void run() { try { start.get(5, TimeUnit.SECONDS); fail("Start should throw exception"); - } catch (ExecutionException ex) { - assertThat(ex.getCause()).isInstanceOf(IOException.class); + } catch (TimeoutException ex) { + assertThat(start.isDone()).isFalse(); } assertThat(selectorManager.getSelectorToUpdateSelector()).isSameInstanceAs(NO_FILTER_CHAIN); } diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index e68d0f5175ce..1bd102db42d7 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -74,6 +74,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Rule; @@ -261,9 +262,10 @@ public void run() { xdsClient.ldsWatcher.onResourceDoesNotExist(ldsResource); try { start.get(5000, TimeUnit.MILLISECONDS); - fail("Start should throw exception"); - } catch (ExecutionException ex) { - assertThat(ex.getCause()).isInstanceOf(IOException.class); + fail("server should not start() successfully."); + } catch (TimeoutException ex) { + // expect to block here. + assertThat(start.isDone()).isFalse(); } verify(mockBuilder, times(1)).build(); verify(mockServer, never()).start(); @@ -602,9 +604,10 @@ public void run() { xdsClient.ldsWatcher.onResourceDoesNotExist(ldsResource); try { start.get(5000, TimeUnit.MILLISECONDS); - fail("Start should throw exception"); - } catch (ExecutionException ex) { - assertThat(ex.getCause()).isInstanceOf(IOException.class); + fail("server should not start()"); + } catch (TimeoutException ex) { + // expect to block here. + assertThat(start.isDone()).isFalse(); } verify(listener, times(1)).onNotServing(any(StatusException.class)); verify(mockBuilder, times(1)).build(); @@ -627,6 +630,13 @@ public void run() { assertThat(sslSupplier0.isShutdown()).isTrue(); xdsClient.deliverRdsUpdate("rds", Collections.singletonList(createVirtualHost("virtual-host-1"))); + try { + start.get(5000, TimeUnit.MILLISECONDS); + fail("Start should throw exception"); + } catch (ExecutionException ex) { + assertThat(ex.getCause()).isInstanceOf(IOException.class); + assertThat(ex.getCause().getMessage()).isEqualTo("error!"); + } RdsResourceWatcher saveRdsWatcher = xdsClient.rdsWatchers.get("rds"); assertThat(executor.forwardNanos(RETRY_DELAY_NANOS)).isEqualTo(1); verify(mockBuilder, times(1)).build(); diff --git a/xds/third_party/udpa/LICENSE b/xds/third_party/xds/LICENSE similarity index 100% rename from xds/third_party/udpa/LICENSE rename to xds/third_party/xds/LICENSE diff --git a/xds/third_party/udpa/import.sh b/xds/third_party/xds/import.sh similarity index 74% rename from xds/third_party/udpa/import.sh rename to xds/third_party/xds/import.sh index b96e4ea6da69..36889a52bba2 100755 --- a/xds/third_party/udpa/import.sh +++ b/xds/third_party/xds/import.sh @@ -18,21 +18,29 @@ set -e BRANCH=main # import VERSION from one of the google internal CLs -VERSION=5459f2c994033b0afed7e4a70ac7e90c90c1ffee -GIT_REPO="https://github.com/cncf/udpa.git" -GIT_BASE_DIR=udpa -SOURCE_PROTO_BASE_DIR=udpa +VERSION=cb28da3451f158a947dfc45090fe92b07b243bc1 +GIT_REPO="https://github.com/cncf/xds.git" +GIT_BASE_DIR=xds +SOURCE_PROTO_BASE_DIR=xds TARGET_PROTO_BASE_DIR=src/main/proto # Sorted alphabetically. FILES=( udpa/annotations/migrate.proto +xds/annotations/v3/migrate.proto udpa/annotations/security.proto +xds/annotations/v3/security.proto +udpa/annotations/security.proto +xds/annotations/v3/security.proto udpa/annotations/sensitive.proto +xds/annotations/v3/sensitive.proto udpa/annotations/status.proto +xds/annotations/v3/status.proto udpa/annotations/versioning.proto -udpa/data/orca/v1/orca_load_report.proto -udpa/service/orca/v1/orca.proto +xds/annotations/v3/versioning.proto +xds/data/orca/v3/orca_load_report.proto +xds/service/orca/v3/orca.proto udpa/type/v1/typed_struct.proto +xds/type/v3/typed_struct.proto xds/core/v3/authority.proto xds/core/v3/collection_entry.proto xds/core/v3/context_params.proto @@ -40,9 +48,9 @@ xds/core/v3/resource_locator.proto xds/core/v3/resource_name.proto ) -pushd `git rev-parse --show-toplevel`/xds/third_party/udpa +pushd `git rev-parse --show-toplevel`/xds/third_party/xds -# clone the udpa github repo in a tmp directory +# clone the xds github repo in a tmp directory tmpdir="$(mktemp -d)" trap "rm -rf $tmpdir" EXIT diff --git a/xds/third_party/udpa/src/main/proto/udpa/annotations/migrate.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto similarity index 86% rename from xds/third_party/udpa/src/main/proto/udpa/annotations/migrate.proto rename to xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto index 1c42a6404dcc..5289cb8a7422 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/annotations/migrate.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto @@ -1,9 +1,15 @@ +// THIS FILE IS DEPRECATED +// Users should instead use the corresponding proto in the xds tree. +// No new changes will be accepted here. + syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; +option go_package = "github.com/cncf/xds/go/annotations"; + // Magic number in this file derived from top 28bit of SHA256 digest of // "udpa.annotation.migrate". diff --git a/xds/third_party/udpa/src/main/proto/udpa/annotations/security.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/security.proto similarity index 82% rename from xds/third_party/udpa/src/main/proto/udpa/annotations/security.proto rename to xds/third_party/xds/src/main/proto/udpa/annotations/security.proto index 7191fe30c827..52801d30d1e7 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/annotations/security.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/security.proto @@ -1,13 +1,16 @@ +// THIS FILE IS DEPRECATED +// Users should instead use the corresponding proto in the xds tree. +// No new changes will be accepted here. + syntax = "proto3"; package udpa.annotations; import "udpa/annotations/status.proto"; -import "google/protobuf/any.proto"; import "google/protobuf/descriptor.proto"; -import "validate/validate.proto"; +option go_package = "github.com/cncf/xds/go/annotations"; // All annotations in this file are experimental and subject to change. Their // only consumer today is the Envoy APIs and SecuritAnnotationValidator protoc diff --git a/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto new file mode 100644 index 000000000000..ab822fb48845 --- /dev/null +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto @@ -0,0 +1,20 @@ +// THIS FILE IS DEPRECATED +// Users should instead use the corresponding proto in the xds tree. +// No new changes will be accepted here. + +syntax = "proto3"; + +package udpa.annotations; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/cncf/xds/go/annotations"; + +extend google.protobuf.FieldOptions { + // Magic number is the 28 most significant bits in the sha256sum of "udpa.annotations.sensitive". + // When set to true, `sensitive` indicates that this field contains sensitive data, such as + // personally identifiable information, passwords, or private keys, and should be redacted for + // display by tools aware of this annotation. Note that that this has no effect on standard + // Protobuf functions such as `TextFormat::PrintToString`. + bool sensitive = 76569463; +} diff --git a/xds/third_party/udpa/src/main/proto/udpa/annotations/status.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/status.proto similarity index 82% rename from xds/third_party/udpa/src/main/proto/udpa/annotations/status.proto rename to xds/third_party/xds/src/main/proto/udpa/annotations/status.proto index 9832ffd3a2fe..76cfd4dcfef5 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/annotations/status.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/status.proto @@ -1,9 +1,15 @@ +// THIS FILE IS DEPRECATED +// Users should instead use the corresponding proto in the xds tree. +// No new changes will be accepted here. + syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; +option go_package = "github.com/cncf/xds/go/annotations"; + // Magic number in this file derived from top 28bit of SHA256 digest of // "udpa.annotation.status". extend google.protobuf.FileOptions { diff --git a/xds/third_party/udpa/src/main/proto/udpa/annotations/versioning.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto similarity index 72% rename from xds/third_party/udpa/src/main/proto/udpa/annotations/versioning.proto rename to xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto index 16f6dc30cad6..dcb7c85fd4f8 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/annotations/versioning.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto @@ -1,9 +1,15 @@ +// THIS FILE IS DEPRECATED +// Users should instead use the corresponding proto in the xds tree. +// No new changes will be accepted here. + syntax = "proto3"; package udpa.annotations; import "google/protobuf/descriptor.proto"; +option go_package = "github.com/cncf/xds/go/annotations"; + extend google.protobuf.MessageOptions { // Magic number derived from 0x78 ('x') 0x44 ('D') 0x53 ('S') VersioningAnnotation versioning = 7881811; diff --git a/xds/third_party/xds/src/main/proto/udpa/type/v1/typed_struct.proto b/xds/third_party/xds/src/main/proto/udpa/type/v1/typed_struct.proto new file mode 100644 index 000000000000..f96625d926fe --- /dev/null +++ b/xds/third_party/xds/src/main/proto/udpa/type/v1/typed_struct.proto @@ -0,0 +1,47 @@ +// THIS FILE IS DEPRECATED +// Users should instead use the corresponding proto in the xds tree. +// No new changes will be accepted here. + +syntax = "proto3"; + +package udpa.type.v1; + +option java_outer_classname = "TypedStructProto"; +option java_multiple_files = true; +option java_package = "com.github.udpa.udpa.type.v1"; +option go_package = "github.com/cncf/xds/go/udpa/type/v1"; + +import "validate/validate.proto"; +import "google/protobuf/struct.proto"; + +// A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that +// describes the type of the serialized message. This is very similar to google.protobuf.Any, +// instead of having protocol buffer binary, this employs google.protobuf.Struct as value. +// +// This message is intended to be embedded inside Any, so it shouldn't be directly referred +// from other UDPA messages. +// +// When packing an opaque extension config, packing the expected type into Any is preferred +// wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor +// is not available, for example if: +// - A control plane sends opaque message that is originally from external source in human readable +// format such as JSON or YAML. +// - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot +// serialize the message in protocol buffer binary format. +// - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension +// uses. This has to be indicated in the DPLB capability negotiation. +// +// When a DPLB receives a TypedStruct in Any, it should: +// - Check if the type_url of the TypedStruct matches the type the extension expects. +// - Convert value to the type described in type_url and perform validation. +// TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link +// protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). +message TypedStruct { + // A URL that uniquely identifies the type of the serialize protocol buffer message. + // This has same semantics and format described in google.protobuf.Any: + // https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + string type_url = 1; + + // A JSON representation of the above specified type. + google.protobuf.Struct value = 2; +} diff --git a/xds/third_party/xds/src/main/proto/xds/annotations/v3/migrate.proto b/xds/third_party/xds/src/main/proto/xds/annotations/v3/migrate.proto new file mode 100644 index 000000000000..13859274c85a --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/annotations/v3/migrate.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package xds.annotations.v3; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/cncf/xds/go/xds/annotations/v3"; + +// Magic number in this file derived from top 28bit of SHA256 digest of +// "xds.annotation.v3.migrate". +extend google.protobuf.MessageOptions { + MigrateAnnotation message_migrate = 112948430; +} +extend google.protobuf.FieldOptions { + FieldMigrateAnnotation field_migrate = 112948430; +} +extend google.protobuf.EnumOptions { + MigrateAnnotation enum_migrate = 112948430; +} +extend google.protobuf.EnumValueOptions { + MigrateAnnotation enum_value_migrate = 112948430; +} +extend google.protobuf.FileOptions { + FileMigrateAnnotation file_migrate = 112948430; +} + +message MigrateAnnotation { + // Rename the message/enum/enum value in next version. + string rename = 1; +} + +message FieldMigrateAnnotation { + // Rename the field in next version. + string rename = 1; + + // Add the field to a named oneof in next version. If this already exists, the + // field will join its siblings under the oneof, otherwise a new oneof will be + // created with the given name. + string oneof_promotion = 2; +} + +message FileMigrateAnnotation { + // Move all types in the file to another package, this implies changing proto + // file path. + string move_to_package = 2; +} diff --git a/xds/third_party/xds/src/main/proto/xds/annotations/v3/security.proto b/xds/third_party/xds/src/main/proto/xds/annotations/v3/security.proto new file mode 100644 index 000000000000..f1f9f40da0aa --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/annotations/v3/security.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package xds.annotations.v3; + +import "xds/annotations/v3/status.proto"; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/cncf/xds/go/xds/annotations/v3"; + +// All annotations in this file are experimental and subject to change. Their +// only consumer today is the Envoy APIs and SecuritAnnotationValidator protoc +// plugin in this repository. +option (xds.annotations.v3.file_status).work_in_progress = true; + +extend google.protobuf.FieldOptions { + // Magic number is the 28 most significant bits in the sha256sum of + // "xds.annotations.v3.security". + FieldSecurityAnnotation security = 99044135; +} + +// These annotations indicate metadata for the purpose of understanding the +// security significance of fields. +message FieldSecurityAnnotation { + // Field should be set in the presence of untrusted downstreams. + bool configure_for_untrusted_downstream = 1; + + // Field should be set in the presence of untrusted upstreams. + bool configure_for_untrusted_upstream = 2; +} diff --git a/xds/third_party/udpa/src/main/proto/udpa/annotations/sensitive.proto b/xds/third_party/xds/src/main/proto/xds/annotations/v3/sensitive.proto similarity index 76% rename from xds/third_party/udpa/src/main/proto/udpa/annotations/sensitive.proto rename to xds/third_party/xds/src/main/proto/xds/annotations/v3/sensitive.proto index 8dc921f24b56..e2cc0b792ef7 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/annotations/sensitive.proto +++ b/xds/third_party/xds/src/main/proto/xds/annotations/v3/sensitive.proto @@ -1,14 +1,16 @@ syntax = "proto3"; -package udpa.annotations; +package xds.annotations.v3; import "google/protobuf/descriptor.proto"; +option go_package = "github.com/cncf/xds/go/xds/annotations/v3"; + extend google.protobuf.FieldOptions { - // Magic number is the 28 most significant bits in the sha256sum of "udpa.annotations.sensitive". + // Magic number is the 28 most significant bits in the sha256sum of "xds.annotations.v3.sensitive". // When set to true, `sensitive` indicates that this field contains sensitive data, such as // personally identifiable information, passwords, or private keys, and should be redacted for // display by tools aware of this annotation. Note that that this has no effect on standard // Protobuf functions such as `TextFormat::PrintToString`. - bool sensitive = 76569463; + bool sensitive = 61008053; } diff --git a/xds/third_party/xds/src/main/proto/xds/annotations/v3/status.proto b/xds/third_party/xds/src/main/proto/xds/annotations/v3/status.proto new file mode 100644 index 000000000000..367e784f671e --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/annotations/v3/status.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package xds.annotations.v3; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/cncf/xds/go/xds/annotations/v3"; + +// Magic number in this file derived from top 28bit of SHA256 digest of +// "xds.annotations.v3.status". +extend google.protobuf.FileOptions { + FileStatusAnnotation file_status = 226829418; +} + +extend google.protobuf.MessageOptions { + MessageStatusAnnotation message_status = 226829418; +} + +extend google.protobuf.FieldOptions { + FieldStatusAnnotation field_status = 226829418; +} + +message FileStatusAnnotation { + // The entity is work-in-progress and subject to breaking changes. + bool work_in_progress = 1; +} + +message MessageStatusAnnotation { + // The entity is work-in-progress and subject to breaking changes. + bool work_in_progress = 1; +} + +message FieldStatusAnnotation { + // The entity is work-in-progress and subject to breaking changes. + bool work_in_progress = 1; +} + +enum PackageVersionStatus { + // Unknown package version status. + UNKNOWN = 0; + + // This version of the package is frozen. + FROZEN = 1; + + // This version of the package is the active development version. + ACTIVE = 2; + + // This version of the package is the candidate for the next major version. It + // is typically machine generated from the active development version. + NEXT_MAJOR_VERSION_CANDIDATE = 3; +} + +message StatusAnnotation { + // The entity is work-in-progress and subject to breaking changes. + bool work_in_progress = 1; + + // The entity belongs to a package with the given version status. + PackageVersionStatus package_version_status = 2; +} diff --git a/xds/third_party/xds/src/main/proto/xds/annotations/v3/versioning.proto b/xds/third_party/xds/src/main/proto/xds/annotations/v3/versioning.proto new file mode 100644 index 000000000000..b6440f1949b2 --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/annotations/v3/versioning.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package xds.annotations.v3; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/cncf/xds/go/xds/annotations/v3"; + +extend google.protobuf.MessageOptions { + // Magic number is the 28 most significant bits in the sha256sum of + // "xds.annotations.v3.versioning". + VersioningAnnotation versioning = 92389011; +} + +message VersioningAnnotation { + // Track the previous message type. E.g. this message might be + // xds.foo.v3alpha.Foo and it was previously xds.bar.v2.Bar. This + // information is consumed by UDPA via proto descriptors. + string previous_message_type = 1; +} diff --git a/xds/third_party/udpa/src/main/proto/xds/core/v3/authority.proto b/xds/third_party/xds/src/main/proto/xds/core/v3/authority.proto similarity index 67% rename from xds/third_party/udpa/src/main/proto/xds/core/v3/authority.proto rename to xds/third_party/xds/src/main/proto/xds/core/v3/authority.proto index 49daf4b8cb64..d666c38eafa1 100644 --- a/xds/third_party/udpa/src/main/proto/xds/core/v3/authority.proto +++ b/xds/third_party/xds/src/main/proto/xds/core/v3/authority.proto @@ -2,15 +2,16 @@ syntax = "proto3"; package xds.core.v3; -import "udpa/annotations/status.proto"; +import "xds/annotations/v3/status.proto"; import "validate/validate.proto"; option java_outer_classname = "AuthorityProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.xds.core.v3"; +option java_package = "com.github.xds.core.v3"; +option go_package = "github.com/cncf/xds/go/xds/core/v3"; -option (udpa.annotations.file_status).work_in_progress = true; +option (xds.annotations.v3.file_status).work_in_progress = true; // xDS authority information. message Authority { diff --git a/xds/third_party/udpa/src/main/proto/xds/core/v3/collection_entry.proto b/xds/third_party/xds/src/main/proto/xds/core/v3/collection_entry.proto similarity index 87% rename from xds/third_party/udpa/src/main/proto/xds/core/v3/collection_entry.proto rename to xds/third_party/xds/src/main/proto/xds/core/v3/collection_entry.proto index 4ece240ca3ec..043a9c421f22 100644 --- a/xds/third_party/udpa/src/main/proto/xds/core/v3/collection_entry.proto +++ b/xds/third_party/xds/src/main/proto/xds/core/v3/collection_entry.proto @@ -4,16 +4,17 @@ package xds.core.v3; import "google/protobuf/any.proto"; -import "udpa/annotations/status.proto"; +import "xds/annotations/v3/status.proto"; import "xds/core/v3/resource_locator.proto"; import "validate/validate.proto"; option java_outer_classname = "CollectionEntryProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.xds.core.v3"; +option java_package = "com.github.xds.core.v3"; +option go_package = "github.com/cncf/xds/go/xds/core/v3"; -option (udpa.annotations.file_status).work_in_progress = true; +option (xds.annotations.v3.file_status).work_in_progress = true; // xDS collection resource wrapper. This encapsulates a xDS resource when // appearing inside a list collection resource. List collection resources are diff --git a/xds/third_party/udpa/src/main/proto/xds/core/v3/context_params.proto b/xds/third_party/xds/src/main/proto/xds/core/v3/context_params.proto similarity index 76% rename from xds/third_party/udpa/src/main/proto/xds/core/v3/context_params.proto rename to xds/third_party/xds/src/main/proto/xds/core/v3/context_params.proto index 8f27c1ca7e53..2a0c079e5d9d 100644 --- a/xds/third_party/udpa/src/main/proto/xds/core/v3/context_params.proto +++ b/xds/third_party/xds/src/main/proto/xds/core/v3/context_params.proto @@ -2,13 +2,14 @@ syntax = "proto3"; package xds.core.v3; -import "udpa/annotations/status.proto"; +import "xds/annotations/v3/status.proto"; option java_outer_classname = "ContextParamsProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.xds.core.v3"; +option java_package = "com.github.xds.core.v3"; +option go_package = "github.com/cncf/xds/go/xds/core/v3"; -option (udpa.annotations.file_status).work_in_progress = true; +option (xds.annotations.v3.file_status).work_in_progress = true; // Additional parameters that can be used to select resource variants. These include any // global context parameters, per-resource type client feature capabilities and per-resource diff --git a/xds/third_party/udpa/src/main/proto/xds/core/v3/resource_locator.proto b/xds/third_party/xds/src/main/proto/xds/core/v3/resource_locator.proto similarity index 95% rename from xds/third_party/udpa/src/main/proto/xds/core/v3/resource_locator.proto rename to xds/third_party/xds/src/main/proto/xds/core/v3/resource_locator.proto index d7334394ae27..9b40d52fc436 100644 --- a/xds/third_party/udpa/src/main/proto/xds/core/v3/resource_locator.proto +++ b/xds/third_party/xds/src/main/proto/xds/core/v3/resource_locator.proto @@ -2,16 +2,17 @@ syntax = "proto3"; package xds.core.v3; -import "udpa/annotations/status.proto"; +import "xds/annotations/v3/status.proto"; import "xds/core/v3/context_params.proto"; import "validate/validate.proto"; option java_outer_classname = "ResourceLocatorProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.xds.core.v3"; +option java_package = "com.github.xds.core.v3"; +option go_package = "github.com/cncf/xds/go/xds/core/v3"; -option (udpa.annotations.file_status).work_in_progress = true; +option (xds.annotations.v3.file_status).work_in_progress = true; // xDS resource locators identify a xDS resource name and instruct the // data-plane load balancer on how the resource may be located. diff --git a/xds/third_party/udpa/src/main/proto/xds/core/v3/resource_name.proto b/xds/third_party/xds/src/main/proto/xds/core/v3/resource_name.proto similarity index 86% rename from xds/third_party/udpa/src/main/proto/xds/core/v3/resource_name.proto rename to xds/third_party/xds/src/main/proto/xds/core/v3/resource_name.proto index 103c7de86402..0f3d9974079b 100644 --- a/xds/third_party/udpa/src/main/proto/xds/core/v3/resource_name.proto +++ b/xds/third_party/xds/src/main/proto/xds/core/v3/resource_name.proto @@ -2,16 +2,17 @@ syntax = "proto3"; package xds.core.v3; -import "udpa/annotations/status.proto"; +import "xds/annotations/v3/status.proto"; import "xds/core/v3/context_params.proto"; import "validate/validate.proto"; option java_outer_classname = "ResourceNameProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.xds.core.v3"; +option java_package = "com.github.xds.core.v3"; +option go_package = "github.com/cncf/xds/go/xds/core/v3"; -option (udpa.annotations.file_status).work_in_progress = true; +option (xds.annotations.v3.file_status).work_in_progress = true; // xDS resource name. This has a canonical xdstp:// URI representation: // diff --git a/xds/third_party/udpa/src/main/proto/udpa/data/orca/v1/orca_load_report.proto b/xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto similarity index 90% rename from xds/third_party/udpa/src/main/proto/udpa/data/orca/v1/orca_load_report.proto rename to xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto index 3105b0947241..9b8f03827dc5 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/data/orca/v1/orca_load_report.proto +++ b/xds/third_party/xds/src/main/proto/xds/data/orca/v3/orca_load_report.proto @@ -1,10 +1,11 @@ syntax = "proto3"; -package udpa.data.orca.v1; +package xds.data.orca.v3; option java_outer_classname = "OrcaLoadReportProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.udpa.data.orca.v1"; +option java_package = "com.github.xds.data.orca.v3"; +option go_package = "github.com/cncf/xds/go/xds/data/orca/v3"; import "validate/validate.proto"; diff --git a/xds/third_party/udpa/src/main/proto/udpa/service/orca/v1/orca.proto b/xds/third_party/xds/src/main/proto/xds/service/orca/v3/orca.proto similarity index 81% rename from xds/third_party/udpa/src/main/proto/udpa/service/orca/v1/orca.proto rename to xds/third_party/xds/src/main/proto/xds/service/orca/v3/orca.proto index d48cccfeacb1..addf62f1ce5e 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/service/orca/v1/orca.proto +++ b/xds/third_party/xds/src/main/proto/xds/service/orca/v3/orca.proto @@ -1,12 +1,13 @@ syntax = "proto3"; -package udpa.service.orca.v1; +package xds.service.orca.v3; option java_outer_classname = "OrcaProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.udpa.service.orca.v1"; +option java_package = "com.github.xds.service.orca.v3"; +option go_package = "github.com/cncf/xds/go/xds/service/orca/v3"; -import "udpa/data/orca/v1/orca_load_report.proto"; +import "xds/data/orca/v3/orca_load_report.proto"; import "google/protobuf/duration.proto"; @@ -24,7 +25,7 @@ import "validate/validate.proto"; // streaming service, client needs to terminate current RPC and initiate // a new call to change backend reporting frequency. service OpenRcaService { - rpc StreamCoreMetrics(OrcaLoadReportRequest) returns (stream udpa.data.orca.v1.OrcaLoadReport); + rpc StreamCoreMetrics(OrcaLoadReportRequest) returns (stream xds.data.orca.v3.OrcaLoadReport); } message OrcaLoadReportRequest { diff --git a/xds/third_party/udpa/src/main/proto/udpa/type/v1/typed_struct.proto b/xds/third_party/xds/src/main/proto/xds/type/v3/typed_struct.proto similarity index 94% rename from xds/third_party/udpa/src/main/proto/udpa/type/v1/typed_struct.proto rename to xds/third_party/xds/src/main/proto/xds/type/v3/typed_struct.proto index e46a2cb57573..5d7226c4bf6b 100644 --- a/xds/third_party/udpa/src/main/proto/udpa/type/v1/typed_struct.proto +++ b/xds/third_party/xds/src/main/proto/xds/type/v3/typed_struct.proto @@ -1,10 +1,11 @@ syntax = "proto3"; -package udpa.type.v1; +package xds.type.v3; option java_outer_classname = "TypedStructProto"; option java_multiple_files = true; -option java_package = "com.github.udpa.udpa.type.v1"; +option java_package = "com.github.xds.type.v3"; +option go_package = "github.com/cncf/xds/go/xds/type/v3"; import "validate/validate.proto"; import "google/protobuf/struct.proto";