diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 6d74006b396..e1f2f17fd29 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -552,6 +552,7 @@ public static final class PickResult { private final Status status; // True if the result is created by withDrop() private final boolean drop; + @Nullable private final String authorityOverride; private PickResult( @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory, @@ -560,6 +561,17 @@ private PickResult( this.streamTracerFactory = streamTracerFactory; this.status = checkNotNull(status, "status"); this.drop = drop; + this.authorityOverride = null; + } + + private PickResult( + @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory, + Status status, boolean drop, @Nullable String authorityOverride) { + this.subchannel = subchannel; + this.streamTracerFactory = streamTracerFactory; + this.status = checkNotNull(status, "status"); + this.drop = drop; + this.authorityOverride = authorityOverride; } /** @@ -639,6 +651,19 @@ public static PickResult withSubchannel( false); } + /** + * Same as {@code withSubchannel(subchannel, streamTracerFactory)} but with an authority name + * to override in the host header. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11656") + public static PickResult withSubchannel( + Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory, + @Nullable String authorityOverride) { + return new PickResult( + checkNotNull(subchannel, "subchannel"), streamTracerFactory, Status.OK, + false, authorityOverride); + } + /** * Equivalent to {@code withSubchannel(subchannel, null)}. * @@ -682,6 +707,13 @@ public static PickResult withNoResult() { return NO_RESULT; } + /** Returns the authority override if any. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11656") + @Nullable + public String getAuthorityOverride() { + return authorityOverride; + } + /** * The Subchannel if this result was created by {@link #withSubchannel withSubchannel()}, or * null otherwise. @@ -736,6 +768,7 @@ public String toString() { .add("streamTracerFactory", streamTracerFactory) .add("status", status) .add("drop", drop) + .add("authority-override", authorityOverride) .toString(); } diff --git a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java index 6eebfdd0fae..ae173f4ac26 100644 --- a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java +++ b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java @@ -131,11 +131,17 @@ public final ClientStream newStream( } if (state.lastPicker != null) { PickResult pickResult = state.lastPicker.pickSubchannel(args); + callOptions = args.getCallOptions(); + // User code provided authority takes precedence over the LB provided one. + if (callOptions.getAuthority() == null + && pickResult.getAuthorityOverride() != null) { + callOptions = callOptions.withAuthority(pickResult.getAuthorityOverride()); + } ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, callOptions.isWaitForReady()); if (transport != null) { return transport.newStream( - args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions(), + args.getMethodDescriptor(), args.getHeaders(), callOptions, tracers); } } @@ -281,6 +287,10 @@ final void reprocess(@Nullable SubchannelPicker picker) { for (final PendingStream stream : toProcess) { PickResult pickResult = picker.pickSubchannel(stream.args); CallOptions callOptions = stream.args.getCallOptions(); + // User code provided authority takes precedence over the LB provided one. + if (callOptions.getAuthority() == null && pickResult.getAuthorityOverride() != null) { + stream.setAuthority(pickResult.getAuthorityOverride()); + } final ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, callOptions.isWaitForReady()); if (transport != null) { diff --git a/core/src/main/java/io/grpc/internal/DelayedStream.java b/core/src/main/java/io/grpc/internal/DelayedStream.java index cc9dd0effc7..c94986a3458 100644 --- a/core/src/main/java/io/grpc/internal/DelayedStream.java +++ b/core/src/main/java/io/grpc/internal/DelayedStream.java @@ -208,7 +208,6 @@ private void delayOrExecute(Runnable runnable) { @Override public void setAuthority(final String authority) { - checkState(listener == null, "May only be called before start"); checkNotNull(authority, "authority"); preStartPendingCalls.add(new Runnable() { @Override diff --git a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java index c7ae8c8b4be..a5160552a9e 100644 --- a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java +++ b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java @@ -502,6 +502,43 @@ public void uncaughtException(Thread t, Throwable e) { verify(transportListener).transportTerminated(); } + @Test + public void reprocess_authorityOverridePresentInCallOptions_authorityOverrideFromLbIsIgnored() { + DelayedStream delayedStream = (DelayedStream) delayedTransport.newStream( + method, headers, callOptions, tracers); + delayedStream.start(mock(ClientStreamListener.class)); + SubchannelPicker picker = mock(SubchannelPicker.class); + PickResult pickResult = PickResult.withSubchannel( + mockSubchannel, null, "authority-override-hostname-from-lb"); + when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(pickResult); + + delayedTransport.reprocess(picker); + fakeExecutor.runDueTasks(); + + verify(mockRealStream, never()).setAuthority("authority-override-hostname-from-lb"); + } + + @Test + public void + reprocess_authorityOverrideNotInCallOptions_authorityOverrideFromLbIsSetIntoStream() { + DelayedStream delayedStream = (DelayedStream) delayedTransport.newStream( + method, headers, callOptions.withAuthority(null), tracers); + delayedStream.start(mock(ClientStreamListener.class)); + SubchannelPicker picker = mock(SubchannelPicker.class); + PickResult pickResult = PickResult.withSubchannel( + mockSubchannel, null, "authority-override-hostname-from-lb"); + when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(pickResult); + when(mockRealTransport.newStream( + same(method), same(headers), any(CallOptions.class), + ArgumentMatchers.any())) + .thenReturn(mockRealStream); + + delayedTransport.reprocess(picker); + fakeExecutor.runDueTasks(); + + verify(mockRealStream).setAuthority("authority-override-hostname-from-lb"); + } + @Test public void reprocess_NoPendingStream() { SubchannelPicker picker = mock(SubchannelPicker.class); @@ -525,6 +562,55 @@ public void reprocess_NoPendingStream() { assertSame(mockRealStream, stream); } + @Test + public void newStream_assignsTransport_authorityFromCallOptionsSupersedesAuthorityFromLB() { + SubchannelPicker picker = mock(SubchannelPicker.class); + AbstractSubchannel subchannel = mock(AbstractSubchannel.class); + when(subchannel.getInternalSubchannel()).thenReturn(mockInternalSubchannel); + PickResult pickResult = PickResult.withSubchannel( + subchannel, null, "authority-override-hostname-from-lb"); + when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(pickResult); + ArgumentCaptor callOptionsArgumentCaptor = + ArgumentCaptor.forClass(CallOptions.class); + when(mockRealTransport.newStream( + any(MethodDescriptor.class), any(Metadata.class), callOptionsArgumentCaptor.capture(), + ArgumentMatchers.any())) + .thenReturn(mockRealStream); + delayedTransport.reprocess(picker); + verifyNoMoreInteractions(picker); + verifyNoMoreInteractions(transportListener); + + CallOptions callOptions = + CallOptions.DEFAULT.withAuthority("authority-override-hosstname-from-calloptions"); + delayedTransport.newStream(method, headers, callOptions, tracers); + assertThat(callOptionsArgumentCaptor.getValue().getAuthority()).isEqualTo( + "authority-override-hosstname-from-calloptions"); + } + + @Test + public void newStream_assignsTransport_authorityFromLB() { + SubchannelPicker picker = mock(SubchannelPicker.class); + AbstractSubchannel subchannel = mock(AbstractSubchannel.class); + when(subchannel.getInternalSubchannel()).thenReturn(mockInternalSubchannel); + PickResult pickResult = PickResult.withSubchannel( + subchannel, null, "authority-override-hostname-from-lb"); + when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(pickResult); + ArgumentCaptor callOptionsArgumentCaptor = + ArgumentCaptor.forClass(CallOptions.class); + when(mockRealTransport.newStream( + any(MethodDescriptor.class), any(Metadata.class), callOptionsArgumentCaptor.capture(), + ArgumentMatchers.any())) + .thenReturn(mockRealStream); + delayedTransport.reprocess(picker); + verifyNoMoreInteractions(picker); + verifyNoMoreInteractions(transportListener); + + CallOptions callOptions = CallOptions.DEFAULT; + delayedTransport.newStream(method, headers, callOptions, tracers); + assertThat(callOptionsArgumentCaptor.getValue().getAuthority()).isEqualTo( + "authority-override-hostname-from-lb"); + } + @Test public void reprocess_newStreamRacesWithReprocess() throws Exception { final CyclicBarrier barrier = new CyclicBarrier(2); diff --git a/core/src/test/java/io/grpc/internal/DelayedStreamTest.java b/core/src/test/java/io/grpc/internal/DelayedStreamTest.java index e39e8d420a2..a47bea9f4ab 100644 --- a/core/src/test/java/io/grpc/internal/DelayedStreamTest.java +++ b/core/src/test/java/io/grpc/internal/DelayedStreamTest.java @@ -84,12 +84,6 @@ public void setStream_setAuthority() { inOrder.verify(realStream).start(any(ClientStreamListener.class)); } - @Test(expected = IllegalStateException.class) - public void setAuthority_afterStart() { - stream.start(listener); - stream.setAuthority("notgonnawork"); - } - @Test(expected = IllegalStateException.class) public void start_afterStart() { stream.start(listener); diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 2a9435aa72f..e4bb28a1b6a 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -34,6 +34,7 @@ import io.grpc.Metadata; import io.grpc.Status; import io.grpc.internal.ForwardingClientStreamTracer; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; import io.grpc.services.MetricReport; import io.grpc.util.ForwardingLoadBalancerHelper; @@ -231,10 +232,16 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { args.getAddresses().get(0).getAttributes()); AtomicReference localityAtomicReference = new AtomicReference<>( clusterLocality); - Attributes attrs = args.getAttributes().toBuilder() - .set(ATTR_CLUSTER_LOCALITY, localityAtomicReference) - .build(); - args = args.toBuilder().setAddresses(addresses).setAttributes(attrs).build(); + Attributes.Builder attrsBuilder = args.getAttributes().toBuilder() + .set(ATTR_CLUSTER_LOCALITY, localityAtomicReference); + if (GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE", false)) { + String hostname = args.getAddresses().get(0).getAttributes() + .get(InternalXdsAttributes.ATTR_ADDRESS_NAME); + if (hostname != null) { + attrsBuilder.set(InternalXdsAttributes.ATTR_ADDRESS_NAME, hostname); + } + } + args = args.toBuilder().setAddresses(addresses).setAttributes(attrsBuilder.build()).build(); final Subchannel subchannel = delegate().createSubchannel(args); return new ForwardingSubchannel() { @@ -389,7 +396,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.category())); } } - final PickResult result = delegate.pickSubchannel(args); + PickResult result = delegate.pickSubchannel(args); if (result.getStatus().isOk() && result.getSubchannel() != null) { if (enableCircuitBreaking) { if (inFlights.get() >= maxConcurrentRequests) { @@ -415,9 +422,17 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { stats, inFlights, result.getStreamTracerFactory()); ClientStreamTracer.Factory orcaTracerFactory = OrcaPerRequestUtil.getInstance() .newOrcaClientStreamTracerFactory(tracerFactory, new OrcaPerRpcListener(stats)); - return PickResult.withSubchannel(result.getSubchannel(), orcaTracerFactory); + result = PickResult.withSubchannel(result.getSubchannel(), + orcaTracerFactory); } } + if (args.getCallOptions().getOption(XdsNameResolver.AUTO_HOST_REWRITE_KEY) != null + && args.getCallOptions().getOption(XdsNameResolver.AUTO_HOST_REWRITE_KEY)) { + result = PickResult.withSubchannel(result.getSubchannel(), + result.getStreamTracerFactory(), + result.getSubchannel().getAttributes().get( + InternalXdsAttributes.ATTR_ADDRESS_NAME)); + } } return result; } diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 875a6c45020..3ef79699b10 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -428,6 +428,7 @@ public void run() { .set(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT, localityLbInfo.localityWeight()) .set(InternalXdsAttributes.ATTR_SERVER_WEIGHT, weight) + .set(InternalXdsAttributes.ATTR_ADDRESS_NAME, endpoint.hostname()) .build(); EquivalentAddressGroup eag = new EquivalentAddressGroup( endpoint.eag().getAddresses(), attr); @@ -567,7 +568,7 @@ void start() { handleEndpointResolutionError(); return; } - resolver.start(new NameResolverListener()); + resolver.start(new NameResolverListener(dnsHostName)); } void refresh() { @@ -606,6 +607,12 @@ public void run() { } private class NameResolverListener extends NameResolver.Listener2 { + private final String dnsHostName; + + NameResolverListener(String dnsHostName) { + this.dnsHostName = dnsHostName; + } + @Override public void onResult(final ResolutionResult resolutionResult) { class NameResolved implements Runnable { @@ -625,6 +632,7 @@ public void run() { Attributes attr = eag.getAttributes().toBuilder() .set(InternalXdsAttributes.ATTR_LOCALITY, LOGICAL_DNS_CLUSTER_LOCALITY) .set(InternalXdsAttributes.ATTR_LOCALITY_NAME, localityName) + .set(InternalXdsAttributes.ATTR_ADDRESS_NAME, dnsHostName) .build(); eag = new EquivalentAddressGroup(eag.getAddresses(), attr); eag = AddressFilter.setPathFilter(eag, Arrays.asList(priorityName, localityName)); diff --git a/xds/src/main/java/io/grpc/xds/Endpoints.java b/xds/src/main/java/io/grpc/xds/Endpoints.java index 8b1715731df..7d7aa3e386d 100644 --- a/xds/src/main/java/io/grpc/xds/Endpoints.java +++ b/xds/src/main/java/io/grpc/xds/Endpoints.java @@ -61,17 +61,19 @@ abstract static class LbEndpoint { // Whether the endpoint is healthy. abstract boolean isHealthy(); + abstract String hostname(); + static LbEndpoint create(EquivalentAddressGroup eag, int loadBalancingWeight, - boolean isHealthy) { - return new AutoValue_Endpoints_LbEndpoint(eag, loadBalancingWeight, isHealthy); + boolean isHealthy, String hostname) { + return new AutoValue_Endpoints_LbEndpoint(eag, loadBalancingWeight, isHealthy, hostname); } // Only for testing. @VisibleForTesting static LbEndpoint create( - String address, int port, int loadBalancingWeight, boolean isHealthy) { + String address, int port, int loadBalancingWeight, boolean isHealthy, String hostname) { return LbEndpoint.create(new EquivalentAddressGroup(new InetSocketAddress(address, port)), - loadBalancingWeight, isHealthy); + loadBalancingWeight, isHealthy, hostname); } } diff --git a/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java b/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java index aaaeb198d21..575bda73f0c 100644 --- a/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java +++ b/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java @@ -91,6 +91,11 @@ public final class InternalXdsAttributes { static final Attributes.Key ATTR_SERVER_WEIGHT = Attributes.Key.create("io.grpc.xds.InternalXdsAttributes.serverWeight"); + /** Name associated with individual address, if available (e.g., DNS name). */ + @EquivalentAddressGroup.Attr + static final Attributes.Key ATTR_ADDRESS_NAME = + Attributes.Key.create("io.grpc.xds.InternalXdsAttributes.addressName"); + /** * Filter chain match for network filters. */ diff --git a/xds/src/main/java/io/grpc/xds/VirtualHost.java b/xds/src/main/java/io/grpc/xds/VirtualHost.java index d9f93dd3a07..7dfa0b34a35 100644 --- a/xds/src/main/java/io/grpc/xds/VirtualHost.java +++ b/xds/src/main/java/io/grpc/xds/VirtualHost.java @@ -166,29 +166,34 @@ abstract static class RouteAction { @Nullable abstract RetryPolicy retryPolicy(); + abstract boolean autoHostRewrite(); + static RouteAction forCluster( String cluster, List hashPolicies, @Nullable Long timeoutNano, - @Nullable RetryPolicy retryPolicy) { + @Nullable RetryPolicy retryPolicy, boolean autoHostRewrite) { checkNotNull(cluster, "cluster"); - return RouteAction.create(hashPolicies, timeoutNano, cluster, null, null, retryPolicy); + return RouteAction.create(hashPolicies, timeoutNano, cluster, null, null, retryPolicy, + autoHostRewrite); } static RouteAction forWeightedClusters( List weightedClusters, List hashPolicies, - @Nullable Long timeoutNano, @Nullable RetryPolicy retryPolicy) { + @Nullable Long timeoutNano, @Nullable RetryPolicy retryPolicy, boolean autoHostRewrite) { checkNotNull(weightedClusters, "weightedClusters"); checkArgument(!weightedClusters.isEmpty(), "empty cluster list"); return RouteAction.create( - hashPolicies, timeoutNano, null, weightedClusters, null, retryPolicy); + hashPolicies, timeoutNano, null, weightedClusters, null, retryPolicy, autoHostRewrite); } static RouteAction forClusterSpecifierPlugin( NamedPluginConfig namedConfig, List hashPolicies, @Nullable Long timeoutNano, - @Nullable RetryPolicy retryPolicy) { + @Nullable RetryPolicy retryPolicy, + boolean autoHostRewrite) { checkNotNull(namedConfig, "namedConfig"); - return RouteAction.create(hashPolicies, timeoutNano, null, null, namedConfig, retryPolicy); + return RouteAction.create(hashPolicies, timeoutNano, null, null, namedConfig, retryPolicy, + autoHostRewrite); } private static RouteAction create( @@ -197,14 +202,16 @@ private static RouteAction create( @Nullable String cluster, @Nullable List weightedClusters, @Nullable NamedPluginConfig namedConfig, - @Nullable RetryPolicy retryPolicy) { + @Nullable RetryPolicy retryPolicy, + boolean autoHostRewrite) { return new AutoValue_VirtualHost_Route_RouteAction( ImmutableList.copyOf(hashPolicies), timeoutNano, cluster, weightedClusters == null ? null : ImmutableList.copyOf(weightedClusters), namedConfig, - retryPolicy); + retryPolicy, + autoHostRewrite); } @AutoValue diff --git a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java index 3ed68ac9b75..6a3cd35bd59 100644 --- a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java @@ -213,7 +213,8 @@ static StructOrError parseLocalityLbEndpoints( || (endpoint.getHealthStatus() == HealthStatus.UNKNOWN); endpoints.add(Endpoints.LbEndpoint.create( new EquivalentAddressGroup(addresses), - endpoint.getLoadBalancingWeight().getValue(), isHealthy)); + endpoint.getLoadBalancingWeight().getValue(), isHealthy, + endpoint.getEndpoint().getHostname())); } return StructOrError.fromStruct(Endpoints.LocalityLbEndpoints.create( endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority())); diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java index af77d128ae7..141580af73d 100644 --- a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -108,13 +108,13 @@ protected LdsUpdate doParse(Args args, Message unpackedMessage) Listener listener = (Listener) unpackedMessage; if (listener.hasApiListener()) { - return processClientSideListener(listener); + return processClientSideListener(listener, args); } else { return processServerSideListener(listener, args); } } - private LdsUpdate processClientSideListener(Listener listener) + private LdsUpdate processClientSideListener(Listener listener, XdsResourceType.Args args) throws ResourceInvalidException { // Unpack HttpConnectionManager from the Listener. HttpConnectionManager hcm; @@ -127,10 +127,10 @@ private LdsUpdate processClientSideListener(Listener listener) "Could not parse HttpConnectionManager config from ApiListener", e); } return LdsUpdate.forApiListener( - parseHttpConnectionManager(hcm, filterRegistry, true /* isForClient */)); + parseHttpConnectionManager(hcm, filterRegistry, true /* isForClient */, args)); } - private LdsUpdate processServerSideListener(Listener proto, Args args) + private LdsUpdate processServerSideListener(Listener proto, XdsResourceType.Args args) throws ResourceInvalidException { Set certProviderInstances = null; if (args.getBootstrapInfo() != null && args.getBootstrapInfo().certProviders() != null) { @@ -138,13 +138,13 @@ private LdsUpdate processServerSideListener(Listener proto, Args args) } return LdsUpdate.forTcpListener(parseServerSideListener(proto, (TlsContextManager) args.getSecurityConfig(), - filterRegistry, certProviderInstances)); + filterRegistry, certProviderInstances, args)); } @VisibleForTesting static EnvoyServerProtoData.Listener parseServerSideListener( Listener proto, TlsContextManager tlsContextManager, - FilterRegistry filterRegistry, Set certProviderInstances) + FilterRegistry filterRegistry, Set certProviderInstances, XdsResourceType.Args args) throws ResourceInvalidException { if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND) && !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) { @@ -182,13 +182,13 @@ static EnvoyServerProtoData.Listener parseServerSideListener( for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { filterChains.add( parseFilterChain(fc, tlsContextManager, filterRegistry, uniqueSet, - certProviderInstances)); + certProviderInstances, args)); } FilterChain defaultFilterChain = null; if (proto.hasDefaultFilterChain()) { defaultFilterChain = parseFilterChain( proto.getDefaultFilterChain(), tlsContextManager, filterRegistry, - null, certProviderInstances); + null, certProviderInstances, args); } return EnvoyServerProtoData.Listener.create( @@ -199,7 +199,7 @@ static EnvoyServerProtoData.Listener parseServerSideListener( static FilterChain parseFilterChain( io.envoyproxy.envoy.config.listener.v3.FilterChain proto, TlsContextManager tlsContextManager, FilterRegistry filterRegistry, - Set uniqueSet, Set certProviderInstances) + Set uniqueSet, Set certProviderInstances, XdsResourceType.Args args) throws ResourceInvalidException { if (proto.getFiltersCount() != 1) { throw new ResourceInvalidException("FilterChain " + proto.getName() @@ -226,7 +226,7 @@ static FilterChain parseFilterChain( + filter.getName() + " failed to unpack message", e); } io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager( - hcmProto, filterRegistry, false /* isForClient */); + hcmProto, filterRegistry, false /* isForClient */, args); EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null; if (proto.hasTransportSocket()) { @@ -458,7 +458,7 @@ private static FilterChainMatch parseFilterChainMatch( @VisibleForTesting static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( HttpConnectionManager proto, FilterRegistry filterRegistry, - boolean isForClient) throws ResourceInvalidException { + boolean isForClient, XdsResourceType.Args args) throws ResourceInvalidException { if (proto.getXffNumTrustedHops() != 0) { throw new ResourceInvalidException( "HttpConnectionManager with xff_num_trusted_hops unsupported"); @@ -515,7 +515,7 @@ static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( // Parse inlined RouteConfiguration or RDS. if (proto.hasRouteConfig()) { List virtualHosts = extractVirtualHosts( - proto.getRouteConfig(), filterRegistry); + proto.getRouteConfig(), filterRegistry, args); return io.grpc.xds.HttpConnectionManager.forVirtualHosts( maxStreamDuration, virtualHosts, filterConfigs); } diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index ca73b7d8451..0beb6dc2483 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -96,6 +96,8 @@ final class XdsNameResolver extends NameResolver { CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY"); static final CallOptions.Key RPC_HASH_KEY = CallOptions.Key.create("io.grpc.xds.RPC_HASH_KEY"); + static final CallOptions.Key AUTO_HOST_REWRITE_KEY = + CallOptions.Key.create("io.grpc.xds.AUTO_HOST_REWRITE_KEY"); @VisibleForTesting static boolean enableTimeout = Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_TIMEOUT")) @@ -217,6 +219,7 @@ public void start(Listener2 listener) { ldsResourceName = XdsClient.canonifyResourceName(ldsResourceName); callCounterProvider = SharedCallCounterMap.getInstance(); resolveState = new ResolveState(ldsResourceName); + resolveState.start(); } @@ -465,14 +468,18 @@ public Result selectConfig(PickSubchannelArgs args) { } final String finalCluster = cluster; final long hash = generateHash(selectedRoute.routeAction().hashPolicies(), headers); + Route finalSelectedRoute = selectedRoute; class ClusterSelectionInterceptor implements ClientInterceptor { @Override public ClientCall interceptCall( final MethodDescriptor method, CallOptions callOptions, final Channel next) { - final CallOptions callOptionsForCluster = + CallOptions callOptionsForCluster = callOptions.withOption(CLUSTER_SELECTION_KEY, finalCluster) .withOption(RPC_HASH_KEY, hash); + if (finalSelectedRoute.routeAction().autoHostRewrite()) { + callOptionsForCluster = callOptionsForCluster.withOption(AUTO_HOST_REWRITE_KEY, true); + } return new SimpleForwardingClientCall( next.newCall(method, callOptionsForCluster)) { @Override diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java index 0a3d1406dac..0e065c6ba9a 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -68,6 +68,9 @@ import javax.annotation.Nullable; class XdsRouteConfigureResource extends XdsResourceType { + + private static final String GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE = + "GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE"; @VisibleForTesting static boolean enableRouteLookup = GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_RLS_LB", true); @@ -128,17 +131,17 @@ protected RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage) throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); } return processRouteConfiguration( - (RouteConfiguration) unpackedMessage, FilterRegistry.getDefaultRegistry()); + (RouteConfiguration) unpackedMessage, FilterRegistry.getDefaultRegistry(), args); } private static RdsUpdate processRouteConfiguration( - RouteConfiguration routeConfig, FilterRegistry filterRegistry) + RouteConfiguration routeConfig, FilterRegistry filterRegistry, XdsResourceType.Args args) throws ResourceInvalidException { - return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry)); + return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, args)); } static List extractVirtualHosts( - RouteConfiguration routeConfig, FilterRegistry filterRegistry) + RouteConfiguration routeConfig, FilterRegistry filterRegistry, XdsResourceType.Args args) throws ResourceInvalidException { Map pluginConfigMap = new HashMap<>(); ImmutableSet.Builder optionalPlugins = ImmutableSet.builder(); @@ -164,7 +167,7 @@ static List extractVirtualHosts( : routeConfig.getVirtualHostsList()) { StructOrError virtualHost = parseVirtualHost(virtualHostProto, filterRegistry, pluginConfigMap, - optionalPlugins.build()); + optionalPlugins.build(), args); if (virtualHost.getErrorDetail() != null) { throw new ResourceInvalidException( "RouteConfiguration contains invalid virtual host: " + virtualHost.getErrorDetail()); @@ -177,12 +180,12 @@ static List extractVirtualHosts( private static StructOrError parseVirtualHost( io.envoyproxy.envoy.config.route.v3.VirtualHost proto, FilterRegistry filterRegistry, Map pluginConfigMap, - Set optionalPlugins) { + Set optionalPlugins, XdsResourceType.Args args) { String name = proto.getName(); List routes = new ArrayList<>(proto.getRoutesCount()); for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) { StructOrError route = parseRoute( - routeProto, filterRegistry, pluginConfigMap, optionalPlugins); + routeProto, filterRegistry, pluginConfigMap, optionalPlugins, args); if (route == null) { continue; } @@ -264,7 +267,7 @@ static StructOrError> parseOverrideFilterConfigs( static StructOrError parseRoute( io.envoyproxy.envoy.config.route.v3.Route proto, FilterRegistry filterRegistry, Map pluginConfigMap, - Set optionalPlugins) { + Set optionalPlugins, XdsResourceType.Args args) { StructOrError routeMatch = parseRouteMatch(proto.getMatch()); if (routeMatch == null) { return null; @@ -288,7 +291,7 @@ static StructOrError parseRoute( case ROUTE: StructOrError routeAction = parseRouteAction(proto.getRoute(), filterRegistry, pluginConfigMap, - optionalPlugins); + optionalPlugins, args); if (routeAction == null) { return null; } @@ -414,7 +417,7 @@ static StructOrError parseHeaderMatcher( static StructOrError parseRouteAction( io.envoyproxy.envoy.config.route.v3.RouteAction proto, FilterRegistry filterRegistry, Map pluginConfigMap, - Set optionalPlugins) { + Set optionalPlugins, XdsResourceType.Args args) { Long timeoutNano = null; if (proto.hasMaxStreamDuration()) { io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration maxStreamDuration @@ -470,7 +473,9 @@ static StructOrError parseRouteAction( switch (proto.getClusterSpecifierCase()) { case CLUSTER: return StructOrError.fromStruct(RouteAction.forCluster( - proto.getCluster(), hashPolicies, timeoutNano, retryPolicy)); + proto.getCluster(), hashPolicies, timeoutNano, retryPolicy, + GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE, false) + && args.getServerInfo().isTrustedXdsServer() && proto.getAutoHostRewrite().getValue())); case CLUSTER_HEADER: return null; case WEIGHTED_CLUSTERS: @@ -502,7 +507,9 @@ static StructOrError parseRouteAction( UnsignedInteger.MAX_VALUE.longValue(), clusterWeightSum)); } return StructOrError.fromStruct(VirtualHost.Route.RouteAction.forWeightedClusters( - weightedClusters, hashPolicies, timeoutNano, retryPolicy)); + weightedClusters, hashPolicies, timeoutNano, retryPolicy, + GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE, false) + && args.getServerInfo().isTrustedXdsServer() && proto.getAutoHostRewrite().getValue())); case CLUSTER_SPECIFIER_PLUGIN: if (enableRouteLookup) { String pluginName = proto.getClusterSpecifierPlugin(); @@ -517,7 +524,10 @@ static StructOrError parseRouteAction( } NamedPluginConfig namedPluginConfig = NamedPluginConfig.create(pluginName, pluginConfig); return StructOrError.fromStruct(VirtualHost.Route.RouteAction.forClusterSpecifierPlugin( - namedPluginConfig, hashPolicies, timeoutNano, retryPolicy)); + namedPluginConfig, hashPolicies, timeoutNano, retryPolicy, + GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE, false) + && args.getServerInfo().isTrustedXdsServer() + && proto.getAutoHostRewrite().getValue())); } else { return null; } diff --git a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java index fe0c0050b52..90babd1e8d0 100644 --- a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java @@ -61,16 +61,19 @@ public abstract static class ServerInfo { public abstract boolean ignoreResourceDeletion(); + public abstract boolean isTrustedXdsServer(); + @VisibleForTesting public static ServerInfo create(String target, @Nullable Object implSpecificConfig) { - return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, false); + return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, false, false); } @VisibleForTesting public static ServerInfo create( - String target, Object implSpecificConfig, boolean ignoreResourceDeletion) { + String target, Object implSpecificConfig, boolean ignoreResourceDeletion, + boolean isTrustedXdsServer) { return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, - ignoreResourceDeletion); + ignoreResourceDeletion, isTrustedXdsServer); } } diff --git a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java index 7ef739c8048..9930417348b 100644 --- a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java @@ -50,6 +50,7 @@ public abstract class BootstrapperImpl extends Bootstrapper { // Server features. private static final String SERVER_FEATURE_IGNORE_RESOURCE_DELETION = "ignore_resource_deletion"; + private static final String SERVER_FEATURE_TRUSTED_XDS_SERVER = "trusted_xds_server"; protected final XdsLogger logger; @@ -240,7 +241,9 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo ignoreResourceDeletion = serverFeatures.contains(SERVER_FEATURE_IGNORE_RESOURCE_DELETION); } servers.add( - ServerInfo.create(serverUri, implSpecificConfig, ignoreResourceDeletion)); + ServerInfo.create(serverUri, implSpecificConfig, ignoreResourceDeletion, + serverFeatures != null + && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER))); } return servers.build(); } diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index 7eba43ce278..0d18af0b04a 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static io.grpc.xds.XdsNameResolver.AUTO_HOST_REWRITE_KEY; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -36,6 +37,7 @@ import io.grpc.InsecureChannelCredentials; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.CreateSubchannelArgs; +import io.grpc.LoadBalancer.FixedResultPicker; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.PickDetailsConsumer; import io.grpc.LoadBalancer.PickResult; @@ -748,6 +750,106 @@ public void endpointAddressesAttachedWithClusterName() { } } + @Test + public void + endpointsWithAuthorityHostname_autoHostRewriteEnabled_pickResultHasAuthorityHostname() { + System.setProperty("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE", "true"); + try { + LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); + WeightedTargetConfig weightedTargetConfig = + buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, + null, Collections.emptyList(), + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig( + weightedTargetProvider, weightedTargetConfig), + null, Collections.emptyMap()); + EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr1", locality, + "authority-host-name"); + deliverAddressesAndConfig(Arrays.asList(endpoint1), config); + assertThat(downstreamBalancers).hasSize(1); // one leaf balancer + FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); + assertThat(leafBalancer.name).isEqualTo("round_robin"); + + // Simulates leaf load balancer creating subchannels. + CreateSubchannelArgs args = + CreateSubchannelArgs.newBuilder() + .setAddresses(leafBalancer.addresses) + .build(); + Subchannel subchannel = leafBalancer.helper.createSubchannel(args); + subchannel.start(infoObject -> { + if (infoObject.getState() == ConnectivityState.READY) { + helper.updateBalancingState( + ConnectivityState.READY, + new FixedResultPicker(PickResult.withSubchannel(subchannel))); + } + }); + assertThat(subchannel.getAttributes().get(InternalXdsAttributes.ATTR_ADDRESS_NAME)).isEqualTo( + "authority-host-name"); + for (EquivalentAddressGroup eag : subchannel.getAllAddresses()) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_ADDRESS_NAME)) + .isEqualTo("authority-host-name"); + } + + leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + assertThat(currentState).isEqualTo(ConnectivityState.READY); + PickDetailsConsumer detailsConsumer = mock(PickDetailsConsumer.class); + pickSubchannelArgs = new PickSubchannelArgsImpl( + TestMethodDescriptors.voidMethod(), new Metadata(), + CallOptions.DEFAULT.withOption(AUTO_HOST_REWRITE_KEY, true), detailsConsumer); + PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); + assertThat(result.getAuthorityOverride()).isEqualTo("authority-host-name"); + } finally { + System.clearProperty("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE"); + } + } + + @Test + public void + endpointWithAuthorityHostname_autoHostRewriteNotEnabled_pickResultNoAuthorityHostname() { + LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); + WeightedTargetConfig weightedTargetConfig = + buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, + null, Collections.emptyList(), + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig( + weightedTargetProvider, weightedTargetConfig), + null, Collections.emptyMap()); + EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr1", locality, + "authority-host-name"); + deliverAddressesAndConfig(Arrays.asList(endpoint1), config); + assertThat(downstreamBalancers).hasSize(1); // one leaf balancer + FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); + assertThat(leafBalancer.name).isEqualTo("round_robin"); + + // Simulates leaf load balancer creating subchannels. + CreateSubchannelArgs args = + CreateSubchannelArgs.newBuilder() + .setAddresses(leafBalancer.addresses) + .build(); + Subchannel subchannel = leafBalancer.helper.createSubchannel(args); + subchannel.start(infoObject -> { + if (infoObject.getState() == ConnectivityState.READY) { + helper.updateBalancingState( + ConnectivityState.READY, + new FixedResultPicker(PickResult.withSubchannel(subchannel))); + } + }); + // Sub Channel wrapper args won't have the address name although addresses will. + assertThat(subchannel.getAttributes().get(InternalXdsAttributes.ATTR_ADDRESS_NAME)).isNull(); + for (EquivalentAddressGroup eag : subchannel.getAllAddresses()) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_ADDRESS_NAME)) + .isEqualTo("authority-host-name"); + } + + leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + assertThat(currentState).isEqualTo(ConnectivityState.READY); + PickDetailsConsumer detailsConsumer = mock(PickDetailsConsumer.class); + pickSubchannelArgs = new PickSubchannelArgsImpl( + TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT, detailsConsumer); + PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); + assertThat(result.getAuthorityOverride()).isNull(); + } + @Test public void endpointAddressesAttachedWithTlsConfig_securityEnabledByDefault() { UpstreamTlsContext upstreamTlsContext = @@ -848,6 +950,11 @@ private WeightedTargetConfig buildWeightedTargetConfig(Map lo * Create a locality-labeled address. */ private static EquivalentAddressGroup makeAddress(final String name, Locality locality) { + return makeAddress(name, locality, null); + } + + private static EquivalentAddressGroup makeAddress(final String name, Locality locality, + String authorityHostname) { class FakeSocketAddress extends SocketAddress { private final String name; @@ -878,12 +985,15 @@ public String toString() { } } + Attributes.Builder attributes = Attributes.newBuilder() + .set(InternalXdsAttributes.ATTR_LOCALITY, locality) + // Unique but arbitrary string + .set(InternalXdsAttributes.ATTR_LOCALITY_NAME, locality.toString()); + if (authorityHostname != null) { + attributes.set(InternalXdsAttributes.ATTR_ADDRESS_NAME, authorityHostname); + } EquivalentAddressGroup eag = new EquivalentAddressGroup(new FakeSocketAddress(name), - Attributes.newBuilder() - .set(InternalXdsAttributes.ATTR_LOCALITY, locality) - // Unique but arbitrary string - .set(InternalXdsAttributes.ATTR_LOCALITY_NAME, locality.toString()) - .build()); + attributes.build()); return AddressFilter.setPathFilter(eag, Collections.singletonList(locality.toString())); } @@ -948,6 +1058,16 @@ public void shutdown() { downstreamBalancers.remove(this); } + void deliverSubchannelState(final Subchannel subchannel, ConnectivityState state) { + SubchannelPicker picker = new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel); + } + }; + helper.updateBalancingState(state, picker); + } + Subchannel createSubChannel() { Subchannel subchannel = helper.createSubchannel( CreateSubchannelArgs.newBuilder().setAddresses(addresses).build()); @@ -1078,6 +1198,7 @@ public void setConnectedEagIndex(int eagIndex) { } private final class FakeXdsClient extends XdsClient { + @Override public ClusterDropStats addClusterDropStats( ServerInfo lrsServerInfo, String clusterName, @Nullable String edsServiceName) { diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 0ecd77b12cb..29c46963da3 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -61,7 +61,6 @@ import io.grpc.internal.ObjectPool; import io.grpc.util.GracefulSwitchLoadBalancer; import io.grpc.util.GracefulSwitchLoadBalancerAccessor; -import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig; import io.grpc.util.OutlierDetectionLoadBalancerProvider; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; @@ -253,13 +252,13 @@ public void edsClustersWithRingHashEndpointLbPolicy() { LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( Arrays.asList( - LbEndpoint.create(endpoint1, 0 /* loadBalancingWeight */, true), - LbEndpoint.create(endpoint2, 0 /* loadBalancingWeight */, true)), + LbEndpoint.create(endpoint1, 0 /* loadBalancingWeight */, true, "hostname1"), + LbEndpoint.create(endpoint2, 0 /* loadBalancingWeight */, true, "hostname2")), 10 /* localityWeight */, 1 /* priority */); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( Collections.singletonList( - LbEndpoint.create(endpoint3, 60 /* loadBalancingWeight */, true)), + LbEndpoint.create(endpoint3, 60 /* loadBalancingWeight */, true, "hostname3")), 50 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, @@ -313,7 +312,7 @@ public void edsClustersWithLeastRequestEndpointLbPolicy() { LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Arrays.asList( - LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true)), + LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true, "hostname1")), 100 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, @@ -329,7 +328,7 @@ public void edsClustersWithLeastRequestEndpointLbPolicy() { PriorityChildConfig priorityChildConfig = Iterables.getOnlyElement(priorityLbConfig.childConfigs.values()); assertThat(GracefulSwitchLoadBalancerAccessor.getChildProvider(priorityChildConfig.childConfig) - .getPolicyName()) + .getPolicyName()) .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig = (ClusterImplConfig) GracefulSwitchLoadBalancerAccessor.getChildConfig(priorityChildConfig.childConfig); @@ -347,7 +346,7 @@ public void edsClustersWithLeastRequestEndpointLbPolicy() { } @Test - public void edsClustersWithOutlierDetection() { + public void edsClustersEndpointHostname_addedToAddressAttribute() { ClusterResolverConfig config = new ClusterResolverConfig( Collections.singletonList(edsDiscoveryMechanismWithOutlierDetection), leastRequest); deliverLbConfig(config); @@ -359,76 +358,17 @@ public void edsClustersWithOutlierDetection() { LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Arrays.asList( - LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true)), + LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true, "hostname1")), 100 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, ImmutableMap.of(locality1, localityLbEndpoints)); assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - assertThat(childBalancer.addresses).hasSize(1); - EquivalentAddressGroup addr = childBalancer.addresses.get(0); - assertThat(addr.getAddresses()).isEqualTo(endpoint.getAddresses()); - assertThat(childBalancer.name).isEqualTo(PRIORITY_POLICY_NAME); - PriorityLbConfig priorityLbConfig = (PriorityLbConfig) childBalancer.config; - assertThat(priorityLbConfig.priorities).containsExactly(CLUSTER1 + "[child1]"); - PriorityChildConfig priorityChildConfig = - Iterables.getOnlyElement(priorityLbConfig.childConfigs.values()); - - // The child config for priority should be outlier detection. - assertThat(GracefulSwitchLoadBalancerAccessor.getChildProvider(priorityChildConfig.childConfig) - .getPolicyName()) - .isEqualTo("outlier_detection_experimental"); - OutlierDetectionLoadBalancerConfig outlierDetectionConfig = (OutlierDetectionLoadBalancerConfig) - GracefulSwitchLoadBalancerAccessor.getChildConfig(priorityChildConfig.childConfig); - - // The outlier detection config should faithfully represent what came down from xDS. - assertThat(outlierDetectionConfig.intervalNanos).isEqualTo(outlierDetection.intervalNanos()); - assertThat(outlierDetectionConfig.baseEjectionTimeNanos).isEqualTo( - outlierDetection.baseEjectionTimeNanos()); - assertThat(outlierDetectionConfig.baseEjectionTimeNanos).isEqualTo( - outlierDetection.baseEjectionTimeNanos()); - assertThat(outlierDetectionConfig.maxEjectionTimeNanos).isEqualTo( - outlierDetection.maxEjectionTimeNanos()); - assertThat(outlierDetectionConfig.maxEjectionPercent).isEqualTo( - outlierDetection.maxEjectionPercent()); - - OutlierDetectionLoadBalancerConfig.SuccessRateEjection successRateEjection - = outlierDetectionConfig.successRateEjection; - assertThat(successRateEjection.stdevFactor).isEqualTo( - outlierDetection.successRateEjection().stdevFactor()); - assertThat(successRateEjection.enforcementPercentage).isEqualTo( - outlierDetection.successRateEjection().enforcementPercentage()); - assertThat(successRateEjection.minimumHosts).isEqualTo( - outlierDetection.successRateEjection().minimumHosts()); - assertThat(successRateEjection.requestVolume).isEqualTo( - outlierDetection.successRateEjection().requestVolume()); - - OutlierDetectionLoadBalancerConfig.FailurePercentageEjection failurePercentageEjection - = outlierDetectionConfig.failurePercentageEjection; - assertThat(failurePercentageEjection.threshold).isEqualTo( - outlierDetection.failurePercentageEjection().threshold()); - assertThat(failurePercentageEjection.enforcementPercentage).isEqualTo( - outlierDetection.failurePercentageEjection().enforcementPercentage()); - assertThat(failurePercentageEjection.minimumHosts).isEqualTo( - outlierDetection.failurePercentageEjection().minimumHosts()); - assertThat(failurePercentageEjection.requestVolume).isEqualTo( - outlierDetection.failurePercentageEjection().requestVolume()); - - // The wrapped configuration should not have been tampered with. - ClusterImplConfig clusterImplConfig = (ClusterImplConfig) - GracefulSwitchLoadBalancerAccessor.getChildConfig(outlierDetectionConfig.childConfig); - assertClusterImplConfig(clusterImplConfig, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, - tlsContext, Collections.emptyList(), WRR_LOCALITY_POLICY_NAME); - WrrLocalityConfig wrrLocalityConfig = (WrrLocalityConfig) - GracefulSwitchLoadBalancerAccessor.getChildConfig(clusterImplConfig.childConfig); - LoadBalancerProvider childProvider = - GracefulSwitchLoadBalancerAccessor.getChildProvider(wrrLocalityConfig.childConfig); - assertThat(childProvider.getPolicyName()).isEqualTo("least_request_experimental"); assertThat( childBalancer.addresses.get(0).getAttributes() - .get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT)).isEqualTo(100); + .get(InternalXdsAttributes.ATTR_ADDRESS_NAME)).isEqualTo("hostname1"); } @@ -449,16 +389,16 @@ public void onlyEdsClusters_receivedEndpoints() { LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( Arrays.asList( - LbEndpoint.create(endpoint1, 100, true), - LbEndpoint.create(endpoint2, 100, true)), + LbEndpoint.create(endpoint1, 100, true, "hostname1"), + LbEndpoint.create(endpoint2, 100, true, "hostname1")), 70 /* localityWeight */, 1 /* priority */); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint3, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint3, 100, true, "hostname2")), 10 /* localityWeight */, 1 /* priority */); LocalityLbEndpoints localityLbEndpoints3 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint4, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint4, 100, true, "hostname3")), 20 /* localityWeight */, 2 /* priority */); String priority1 = CLUSTER2 + "[child1]"; String priority2 = CLUSTER2 + "[child2]"; @@ -613,8 +553,8 @@ locality2, createEndpoints(1) private LocalityLbEndpoints createEndpoints(int priority) { return LocalityLbEndpoints.create( Arrays.asList( - LbEndpoint.create(makeAddress("endpoint-addr-1"), 100, true), - LbEndpoint.create(makeAddress("endpoint-addr-2"), 100, true)), + LbEndpoint.create(makeAddress("endpoint-addr-1"), 100, true, "hostname1"), + LbEndpoint.create(makeAddress("endpoint-addr-2"), 100, true, "hostname2")), 70 /* localityWeight */, priority /* priority */); } @@ -652,11 +592,11 @@ public void onlyEdsClusters_allResourcesRevoked_shutDownChildLbPolicy() { EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint1, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint1, 100, true, "hostname1")), 10 /* localityWeight */, 1 /* priority */); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint2, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint2, 100, true, "hostname2")), 20 /* localityWeight */, 2 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints1)); @@ -686,8 +626,8 @@ public void handleEdsResource_ignoreUnhealthyEndpoints() { LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Arrays.asList( - LbEndpoint.create(endpoint1, 100, false /* isHealthy */), - LbEndpoint.create(endpoint2, 100, true /* isHealthy */)), + LbEndpoint.create(endpoint1, 100, false /* isHealthy */, "hostname1"), + LbEndpoint.create(endpoint2, 100, true /* isHealthy */, "hostname2")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); @@ -705,11 +645,13 @@ public void handleEdsResource_ignoreLocalitiesWithNoHealthyEndpoints() { EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint1, 100, false /* isHealthy */)), + Collections.singletonList(LbEndpoint.create(endpoint1, 100, false /* isHealthy */, + "hostname1")), 10 /* localityWeight */, 1 /* priority */); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint2, 100, true /* isHealthy */)), + Collections.singletonList(LbEndpoint.create(endpoint2, 100, true /* isHealthy */, + "hostname2")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, @@ -730,11 +672,13 @@ public void handleEdsResource_ignorePrioritiesWithNoHealthyEndpoints() { EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint1, 100, false /* isHealthy */)), + Collections.singletonList(LbEndpoint.create(endpoint1, 100, false /* isHealthy */, + "hostname1")), 10 /* localityWeight */, 1 /* priority */); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint2, 200, true /* isHealthy */)), + Collections.singletonList(LbEndpoint.create(endpoint2, 200, true /* isHealthy */, + "hostname2")), 10 /* localityWeight */, 2 /* priority */); String priority2 = CLUSTER1 + "[child2]"; xdsClient.deliverClusterLoadAssignment( @@ -753,7 +697,8 @@ public void handleEdsResource_noHealthyEndpoint() { EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint, 100, false /* isHealthy */)), + Collections.singletonList(LbEndpoint.create(endpoint, 100, false /* isHealthy */, + "hostname1")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment(EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); // single endpoint, unhealthy @@ -794,6 +739,11 @@ public void onlyLogicalDnsCluster_endpointsResolved() { assertClusterImplConfig(clusterImplConfig, CLUSTER_DNS, null, LRS_SERVER_INFO, 300L, null, Collections.emptyList(), "pick_first"); assertAddressesEqual(Arrays.asList(endpoint1, endpoint2), childBalancer.addresses); + assertThat(childBalancer.addresses.get(0).getAttributes() + .get(InternalXdsAttributes.ATTR_ADDRESS_NAME)).isEqualTo(DNS_HOST_NAME); + assertThat(childBalancer.addresses.get(1).getAttributes() + .get(InternalXdsAttributes.ATTR_ADDRESS_NAME)).isEqualTo(DNS_HOST_NAME); + } @Test @@ -912,7 +862,7 @@ public void edsClustersAndLogicalDnsCluster_receivedEndpoints() { resolver.deliverEndpointAddresses(Arrays.asList(endpoint1, endpoint2)); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint3, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint3, 100, true, "hostname3")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); @@ -969,7 +919,7 @@ public void edsResourceRevoked_dnsResolutionError_shutDownChildLbPolicyAndReturn EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint, 100, true, "hostname1")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); @@ -1000,7 +950,7 @@ public void resolutionErrorAfterChildLbCreated_propagateErrorIfAllClustersEncoun EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint, 100, true, "hostname1")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); @@ -1093,7 +1043,7 @@ public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThroug EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint1, 100, true)), + Collections.singletonList(LbEndpoint.create(endpoint1, 100, true, "hostname1")), 10 /* localityWeight */, 1 /* priority */); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); @@ -1202,6 +1152,7 @@ public String toString() { } private static final class FakeXdsClient extends XdsClient { + private final Map> watchers = new HashMap<>(); @Override diff --git a/xds/src/test/java/io/grpc/xds/ControlPlaneRule.java b/xds/src/test/java/io/grpc/xds/ControlPlaneRule.java index 1ddf9620434..69fde29a0a9 100644 --- a/xds/src/test/java/io/grpc/xds/ControlPlaneRule.java +++ b/xds/src/test/java/io/grpc/xds/ControlPlaneRule.java @@ -22,7 +22,9 @@ import static io.grpc.xds.XdsTestControlPlaneService.ADS_TYPE_URL_RDS; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.protobuf.Any; +import com.google.protobuf.BoolValue; import com.google.protobuf.Message; import com.google.protobuf.UInt32Value; import io.envoyproxy.envoy.config.cluster.v3.Cluster; @@ -159,7 +161,7 @@ public Server getServer() { "channel_creds", Collections.singletonList( ImmutableMap.of("type", "insecure") ), - "server_features", Collections.singletonList("xds_v3") + "server_features", Lists.newArrayList("xds_v3", "trusted_xds_server") ) ), "server_listener_resource_name_template", SERVER_LISTENER_TEMPLATE_NO_REPLACEMENT @@ -197,7 +199,9 @@ static RouteConfiguration buildRouteConfiguration(String authority) { .setMatch( RouteMatch.newBuilder().setPrefix("/").build()) .setRoute( - RouteAction.newBuilder().setCluster(CLUSTER_NAME).build()).build()).build(); + RouteAction.newBuilder().setCluster(CLUSTER_NAME) + .setAutoHostRewrite(BoolValue.newBuilder().setValue(true).build()) + .build()).build()).build(); return RouteConfiguration.newBuilder().setName(RDS_NAME).addVirtualHosts(virtualHost).build(); } @@ -223,7 +227,8 @@ static Cluster buildCluster() { /** * Builds a new default EDS configuration. */ - static ClusterLoadAssignment buildClusterLoadAssignment(String hostName, int port) { + static ClusterLoadAssignment buildClusterLoadAssignment(String hostName, String endpointHostname, + int port) { Address address = Address.newBuilder() .setSocketAddress( SocketAddress.newBuilder().setAddress(hostName).setPortValue(port).build()).build(); @@ -233,7 +238,8 @@ static ClusterLoadAssignment buildClusterLoadAssignment(String hostName, int por .addLbEndpoints( LbEndpoint.newBuilder() .setEndpoint( - Endpoint.newBuilder().setAddress(address).build()) + Endpoint.newBuilder() + .setAddress(address).setHostname(endpointHostname).build()) .setHealthStatus(HealthStatus.HEALTHY) .build()).build(); return ClusterLoadAssignment.newBuilder() diff --git a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java index 63b9cda043c..8166027033f 100644 --- a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java +++ b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java @@ -506,7 +506,6 @@ public Collection getSubscribedResources(ServerInfo serverInfo, public Map> getSubscribedResourceTypesWithTypeUrl() { return ImmutableMap.of(); } - } private static class FakeXdsClientPoolFactory implements XdsClientPoolFactory { diff --git a/xds/src/test/java/io/grpc/xds/DataPlaneRule.java b/xds/src/test/java/io/grpc/xds/DataPlaneRule.java index faa79444071..b308419d142 100644 --- a/xds/src/test/java/io/grpc/xds/DataPlaneRule.java +++ b/xds/src/test/java/io/grpc/xds/DataPlaneRule.java @@ -48,6 +48,7 @@ public class DataPlaneRule extends TestWatcher { private static final Logger logger = Logger.getLogger(DataPlaneRule.class.getName()); private static final String SERVER_HOST_NAME = "test-server"; + static final String ENDPOINT_HOST_NAME = "endpoint-host-name"; private static final String SCHEME = "test-xds"; private final ControlPlaneRule controlPlane; @@ -73,7 +74,8 @@ public Server getServer() { */ public ManagedChannel getManagedChannel() { ManagedChannel channel = Grpc.newChannelBuilder(SCHEME + ":///" + SERVER_HOST_NAME, - InsecureChannelCredentials.create()).build(); + InsecureChannelCredentials.create()) + .build(); channels.add(channel); return channel; } @@ -98,7 +100,7 @@ protected void starting(Description description) { InetSocketAddress edsInetSocketAddress = (InetSocketAddress) server.getListenSockets().get(0); controlPlane.setEdsConfig( ControlPlaneRule.buildClusterLoadAssignment(edsInetSocketAddress.getHostName(), - edsInetSocketAddress.getPort())); + ENDPOINT_HOST_NAME, edsInetSocketAddress.getPort())); } @Override @@ -124,10 +126,12 @@ protected void finished(Description description) { } private void startServer(Map bootstrapOverride) throws Exception { + final String[] authority = new String[1]; ServerInterceptor metadataInterceptor = new ServerInterceptor() { @Override public ServerCall.Listener interceptCall(ServerCall call, Metadata requestHeaders, ServerCallHandler next) { + authority[0] = call.getAuthority(); logger.fine("Received following metadata: " + requestHeaders); // Make a copy of the headers so that it can be read in a thread-safe manner when copying @@ -155,8 +159,12 @@ public void close(Status status, Metadata trailers) { @Override public void unaryRpc( SimpleRequest request, StreamObserver responseObserver) { + String responseMsg = "Hi, xDS!"; + if (authority[0] != null) { + responseMsg += " Authority= " + authority[0]; + } SimpleResponse response = - SimpleResponse.newBuilder().setResponseMessage("Hi, xDS!").build(); + SimpleResponse.newBuilder().setResponseMessage(responseMsg).build(); responseObserver.onNext(response); responseObserver.onCompleted(); } diff --git a/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java b/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java index 30c2403396e..a3106bd20ae 100644 --- a/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java @@ -18,6 +18,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.DataPlaneRule.ENDPOINT_HOST_NAME; import static org.junit.Assert.assertEquals; import com.github.xds.type.v3.TypedStruct; @@ -91,11 +92,29 @@ public void pingPong() throws Exception { SimpleRequest request = SimpleRequest.newBuilder() .build(); SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setResponseMessage("Hi, xDS!") + .setResponseMessage("Hi, xDS! Authority= test-server") .build(); assertEquals(goldenResponse, blockingStub.unaryRpc(request)); } + @Test + public void pingPong_edsEndpoint_authorityOverride() throws Exception { + System.setProperty("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE", "true"); + try { + ManagedChannel channel = dataPlane.getManagedChannel(); + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.newBlockingStub( + channel); + SimpleRequest request = SimpleRequest.newBuilder() + .build(); + SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setResponseMessage("Hi, xDS! Authority= " + ENDPOINT_HOST_NAME) + .build(); + assertEquals(goldenResponse, blockingStub.unaryRpc(request)); + } finally { + System.clearProperty("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE"); + } + } + @Test public void pingPong_metadataLoadBalancer() throws Exception { MetadataLoadBalancerProvider metadataLbProvider = new MetadataLoadBalancerProvider(); @@ -129,7 +148,7 @@ public void pingPong_metadataLoadBalancer() throws Exception { SimpleRequest request = SimpleRequest.newBuilder() .build(); SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setResponseMessage("Hi, xDS!") + .setResponseMessage("Hi, xDS! Authority= test-server") .build(); assertEquals(goldenResponse, blockingStub.unaryRpc(request)); @@ -183,38 +202,43 @@ public void pingPong_ringHash() { SimpleRequest request = SimpleRequest.newBuilder() .build(); SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setResponseMessage("Hi, xDS!") + .setResponseMessage("Hi, xDS! Authority= test-server") .build(); assertEquals(goldenResponse, blockingStub.unaryRpc(request)); } @Test - public void pingPong_logicalDns() { - InetSocketAddress serverAddress = - (InetSocketAddress) dataPlane.getServer().getListenSockets().get(0); - controlPlane.setCdsConfig( - ControlPlaneRule.buildCluster().toBuilder() - .setType(Cluster.DiscoveryType.LOGICAL_DNS) - .setLoadAssignment( - ClusterLoadAssignment.newBuilder().addEndpoints( - LocalityLbEndpoints.newBuilder().addLbEndpoints( - LbEndpoint.newBuilder().setEndpoint( - Endpoint.newBuilder().setAddress( - Address.newBuilder().setSocketAddress( - SocketAddress.newBuilder() - .setAddress("localhost") - .setPortValue(serverAddress.getPort())))))) - .build()) - .build()); - - ManagedChannel channel = dataPlane.getManagedChannel(); - SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.newBlockingStub( - channel); - SimpleRequest request = SimpleRequest.newBuilder() - .build(); - SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setResponseMessage("Hi, xDS!") - .build(); - assertEquals(goldenResponse, blockingStub.unaryRpc(request)); + public void pingPong_logicalDns_authorityOverride() { + System.setProperty("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE", "true"); + try { + InetSocketAddress serverAddress = + (InetSocketAddress) dataPlane.getServer().getListenSockets().get(0); + controlPlane.setCdsConfig( + ControlPlaneRule.buildCluster().toBuilder() + .setType(Cluster.DiscoveryType.LOGICAL_DNS) + .setLoadAssignment( + ClusterLoadAssignment.newBuilder().addEndpoints( + LocalityLbEndpoints.newBuilder().addLbEndpoints( + LbEndpoint.newBuilder().setEndpoint( + Endpoint.newBuilder().setAddress( + Address.newBuilder().setSocketAddress( + SocketAddress.newBuilder() + .setAddress("localhost") + .setPortValue(serverAddress.getPort())))))) + .build()) + .build()); + + ManagedChannel channel = dataPlane.getManagedChannel(); + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.newBlockingStub( + channel); + SimpleRequest request = SimpleRequest.newBuilder() + .build(); + SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setResponseMessage("Hi, xDS! Authority= localhost:" + serverAddress.getPort()) + .build(); + assertEquals(goldenResponse, blockingStub.unaryRpc(request)); + } finally { + System.clearProperty("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE"); + } } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java index 30ea76b54f2..475b6e00a07 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java @@ -627,6 +627,28 @@ public void serverFeatureIgnoreResourceDeletion() throws XdsInitializationExcept assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } + @Test + public void serverFeatureTrustedXdsServer() throws XdsInitializationException { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"server_features\": [\"trusted_xds_server\"]\n" + + " }\n" + + " ]\n" + + "}"; + + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(serverInfo.target()).isEqualTo(SERVER_URI); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.isTrustedXdsServer()).isTrue(); + } + @Test public void serverFeatureIgnoreResourceDeletion_xdsV3() throws XdsInitializationException { String rawData = "{\n" diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java index 3c159ba7055..cb5bd2db6ec 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java @@ -166,6 +166,8 @@ public class GrpcXdsClientImplDataTest { private static final ServerInfo LRS_SERVER_INFO = ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create()); + private static final String GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE = + "GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE"; @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467 @Rule @@ -200,7 +202,7 @@ public void parseRoute_withRouteAction() { .setCluster("cluster-foo")) .build(); StructOrError struct = XdsRouteConfigureResource.parseRoute( - proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of()); + proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) .isEqualTo( @@ -208,7 +210,7 @@ public void parseRoute_withRouteAction() { RouteMatch.create(PathMatcher.fromPath("/service/method", false), Collections.emptyList(), null), RouteAction.forCluster( - "cluster-foo", Collections.emptyList(), null, null), + "cluster-foo", Collections.emptyList(), null, null, false), ImmutableMap.of())); } @@ -223,7 +225,7 @@ public void parseRoute_withNonForwardingAction() { .setNonForwardingAction(NonForwardingAction.getDefaultInstance()) .build(); StructOrError struct = XdsRouteConfigureResource.parseRoute( - proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of()); + proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct()) .isEqualTo( Route.forNonForwardingAction( @@ -242,7 +244,8 @@ public void parseRoute_withUnsupportedActionTypes() { .setRedirect(RedirectAction.getDefaultInstance()) .build(); res = XdsRouteConfigureResource.parseRoute( - redirectRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of()); + redirectRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), + getXdsResourceTypeArgs(true)); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) .isEqualTo("Route [route-blade] with unknown action type: REDIRECT"); @@ -254,7 +257,8 @@ public void parseRoute_withUnsupportedActionTypes() { .setDirectResponse(DirectResponseAction.getDefaultInstance()) .build(); res = XdsRouteConfigureResource.parseRoute( - directResponseRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of()); + directResponseRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), + getXdsResourceTypeArgs(true)); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) .isEqualTo("Route [route-blade] with unknown action type: DIRECT_RESPONSE"); @@ -266,7 +270,8 @@ public void parseRoute_withUnsupportedActionTypes() { .setFilterAction(FilterAction.getDefaultInstance()) .build(); res = XdsRouteConfigureResource.parseRoute( - filterRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of()); + filterRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), + getXdsResourceTypeArgs(true)); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) .isEqualTo("Route [route-blade] with unknown action type: FILTER_ACTION"); @@ -288,7 +293,8 @@ public void parseRoute_skipRouteWithUnsupportedMatcher() { .setCluster("cluster-foo")) .build(); assertThat(XdsRouteConfigureResource.parseRoute( - proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of())) + proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), + getXdsResourceTypeArgs(true))) .isNull(); } @@ -305,7 +311,8 @@ public void parseRoute_skipRouteWithUnsupportedAction() { .setClusterHeader("cluster header")) // cluster_header action not supported .build(); assertThat(XdsRouteConfigureResource.parseRoute( - proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of())) + proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of(), + getXdsResourceTypeArgs(true))) .isNull(); } @@ -515,10 +522,48 @@ public void parseRouteAction_withCluster() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo"); assertThat(struct.getStruct().weightedClusters()).isNull(); + assertThat(struct.getStruct().autoHostRewrite()).isFalse(); + } + + @Test + public void parseRouteAction_withCluster_autoHostRewriteEnabled() { + System.setProperty(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE, "true"); + try { + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setCluster("cluster-foo") + .setAutoHostRewrite(BoolValue.of(true)) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); + assertThat(struct.getErrorDetail()).isNull(); + assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo"); + assertThat(struct.getStruct().weightedClusters()).isNull(); + assertThat(struct.getStruct().autoHostRewrite()).isTrue(); + } finally { + System.clearProperty(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE); + } + } + + @Test + public void parseRouteAction_withCluster_flagDisabled_autoHostRewriteNotEnabled() { + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setCluster("cluster-foo") + .setAutoHostRewrite(BoolValue.of(true)) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); + assertThat(struct.getErrorDetail()).isNull(); + assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo"); + assertThat(struct.getStruct().weightedClusters()).isNull(); + assertThat(struct.getStruct().autoHostRewrite()).isFalse(); } @Test @@ -539,12 +584,74 @@ public void parseRouteAction_withWeightedCluster() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); + assertThat(struct.getErrorDetail()).isNull(); + assertThat(struct.getStruct().cluster()).isNull(); + assertThat(struct.getStruct().weightedClusters()).containsExactly( + ClusterWeight.create("cluster-foo", 30, ImmutableMap.of()), + ClusterWeight.create("cluster-bar", 70, ImmutableMap.of())); + assertThat(struct.getStruct().autoHostRewrite()).isFalse(); + } + + @Test + public void parseRouteAction_withWeightedCluster_autoHostRewriteEnabled() { + System.setProperty(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE, "true"); + try { + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setWeightedClusters( + WeightedCluster.newBuilder() + .addClusters( + WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-foo") + .setWeight(UInt32Value.newBuilder().setValue(30))) + .addClusters(WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-bar") + .setWeight(UInt32Value.newBuilder().setValue(70)))) + .setAutoHostRewrite(BoolValue.of(true)) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); + assertThat(struct.getErrorDetail()).isNull(); + assertThat(struct.getStruct().cluster()).isNull(); + assertThat(struct.getStruct().weightedClusters()).containsExactly( + ClusterWeight.create("cluster-foo", 30, ImmutableMap.of()), + ClusterWeight.create("cluster-bar", 70, ImmutableMap.of())); + assertThat(struct.getStruct().autoHostRewrite()).isTrue(); + } finally { + System.clearProperty(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE); + } + } + + @Test + public void parseRouteAction_withWeightedCluster_flagDisabled_autoHostRewriteDisabled() { + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setWeightedClusters( + WeightedCluster.newBuilder() + .addClusters( + WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-foo") + .setWeight(UInt32Value.newBuilder().setValue(30))) + .addClusters(WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-bar") + .setWeight(UInt32Value.newBuilder().setValue(70)))) + .setAutoHostRewrite(BoolValue.of(true)) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct().cluster()).isNull(); assertThat(struct.getStruct().weightedClusters()).containsExactly( ClusterWeight.create("cluster-foo", 30, ImmutableMap.of()), ClusterWeight.create("cluster-bar", 70, ImmutableMap.of())); + assertThat(struct.getStruct().autoHostRewrite()).isFalse(); } @Test @@ -565,7 +672,7 @@ public void parseRouteAction_weightedClusterSum() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()).isEqualTo("Sum of cluster weights should be above 0."); } @@ -581,7 +688,7 @@ public void parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L)); } @@ -596,7 +703,7 @@ public void parseRouteAction_withTimeoutByMaxStreamDuration() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L)); } @@ -608,7 +715,7 @@ public void parseRouteAction_withTimeoutUnset() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().timeoutNano()).isNull(); } @@ -630,7 +737,7 @@ public void parseRouteAction_withRetryPolicy() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); RouteAction.RetryPolicy retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.maxAttempts()).isEqualTo(4); assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(500)); @@ -654,7 +761,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder.build()) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().retryPolicy()).isNotNull(); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()).isEmpty(); @@ -667,7 +774,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()).isEqualTo("No base_interval specified in retry_backoff"); // max_interval unset @@ -677,7 +784,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(500 * 10)); @@ -688,7 +795,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()) .isEqualTo("base_interval in retry_backoff must be positive"); @@ -701,7 +808,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()) .isEqualTo("max_interval in retry_backoff cannot be less than base_interval"); @@ -714,7 +821,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getErrorDetail()) .isEqualTo("max_interval in retry_backoff cannot be less than base_interval"); @@ -727,7 +834,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().retryPolicy().initialBackoff()) .isEqualTo(Durations.fromMillis(1)); assertThat(struct.getStruct().retryPolicy().maxBackoff()) @@ -743,7 +850,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(25)); assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(250)); @@ -762,7 +869,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -780,7 +887,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -798,7 +905,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder) .build(); struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); } @@ -837,7 +944,7 @@ public void parseRouteAction_withHashPolicies() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); List policies = struct.getStruct().hashPolicies(); assertThat(policies).hasSize(2); assertThat(policies.get(0).type()).isEqualTo(HashPolicy.Type.HEADER); @@ -857,7 +964,7 @@ public void parseRouteAction_custerSpecifierNotSet() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct).isNull(); } @@ -870,10 +977,65 @@ public void parseRouteAction_clusterSpecifier_routeLookupDisabled() { .build(); StructOrError struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, - ImmutableMap.of(), ImmutableSet.of()); + ImmutableMap.of(), ImmutableSet.of(), getXdsResourceTypeArgs(true)); assertThat(struct).isNull(); } + @Test + public void parseRouteAction_clusterSpecifier() { + XdsRouteConfigureResource.enableRouteLookup = true; + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setClusterSpecifierPlugin(CLUSTER_SPECIFIER_PLUGIN.name()) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(CLUSTER_SPECIFIER_PLUGIN.name(), RlsPluginConfig.create( + ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"))), ImmutableSet.of(), + getXdsResourceTypeArgs(true)); + assertThat(struct.getStruct()).isNotNull(); + assertThat(struct.getStruct().autoHostRewrite()).isFalse(); + } + + @Test + public void parseRouteAction_clusterSpecifier_autoHostRewriteEnabled() { + System.setProperty(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE, "true"); + try { + XdsRouteConfigureResource.enableRouteLookup = true; + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setClusterSpecifierPlugin(CLUSTER_SPECIFIER_PLUGIN.name()) + .setAutoHostRewrite(BoolValue.of(true)) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(CLUSTER_SPECIFIER_PLUGIN.name(), RlsPluginConfig.create( + ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"))), ImmutableSet.of(), + getXdsResourceTypeArgs(true)); + assertThat(struct.getStruct()).isNotNull(); + assertThat(struct.getStruct().autoHostRewrite()).isTrue(); + } finally { + System.clearProperty(GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE); + } + } + + @Test + public void parseRouteAction_clusterSpecifier_flagDisabled_autoHostRewriteDisabled() { + XdsRouteConfigureResource.enableRouteLookup = true; + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setClusterSpecifierPlugin(CLUSTER_SPECIFIER_PLUGIN.name()) + .setAutoHostRewrite(BoolValue.of(true)) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, + ImmutableMap.of(CLUSTER_SPECIFIER_PLUGIN.name(), RlsPluginConfig.create( + ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"))), ImmutableSet.of(), + getXdsResourceTypeArgs(true)); + assertThat(struct.getStruct()).isNotNull(); + assertThat(struct.getStruct().autoHostRewrite()).isFalse(); + } + @Test public void parseClusterWeight() { io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto = @@ -908,7 +1070,8 @@ public void parseLocalityLbEndpoints_withHealthyEndpoints() { assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1)); + Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true, "")), + 100, 1)); } @Test @@ -932,7 +1095,8 @@ public void parseLocalityLbEndpoints_treatUnknownHealthAsHealthy() { assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1)); + Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true, "")), 100, + 1)); } @Test @@ -956,7 +1120,8 @@ public void parseLocalityLbEndpoints_withUnHealthyEndpoints() { assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, false)), 100, 1)); + Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, false, "")), 100, + 1)); } @Test @@ -1017,7 +1182,7 @@ public void parseLocalityLbEndpoints_withDualStackEndpoints() { EquivalentAddressGroup expectedEag = new EquivalentAddressGroup(socketAddressList); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(expectedEag, 20, true)), 100, 1)); + Collections.singletonList(LbEndpoint.create(expectedEag, 20, true, "")), 100, 1)); } finally { if (originalDualStackProp != null) { System.setProperty(GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS, originalDualStackProp); @@ -1396,7 +1561,7 @@ public void parseHttpConnectionManager_xffNumTrustedHopsUnsupported() thrown.expectMessage("HttpConnectionManager with xff_num_trusted_hops unsupported"); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1409,7 +1574,7 @@ public void parseHttpConnectionManager_OriginalIpDetectionExtensionsMustEmpty() thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with original_ip_detection_extensions unsupported"); XdsListenerResource.parseHttpConnectionManager( - hcm, filterRegistry, false); + hcm, filterRegistry, false, getXdsResourceTypeArgs(true)); } @Test @@ -1428,7 +1593,7 @@ public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration() thrown.expectMessage("HttpConnectionManager neither has inlined route_config nor RDS"); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1447,7 +1612,7 @@ public void parseHttpConnectionManager_duplicateHttpFilters() throws ResourceInv thrown.expectMessage("HttpConnectionManager contains duplicate HttpFilter: envoy.filter.foo"); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1465,7 +1630,7 @@ public void parseHttpConnectionManager_lastNotTerminal() throws ResourceInvalidE thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1483,7 +1648,7 @@ public void parseHttpConnectionManager_terminalNotLast() throws ResourceInvalidE thrown.expectMessage("A terminal HttpFilter must be the last filter: terminal"); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true); + true, getXdsResourceTypeArgs(true)); } @Test @@ -1499,7 +1664,7 @@ public void parseHttpConnectionManager_unknownFilters() throws ResourceInvalidEx thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1511,7 +1676,7 @@ public void parseHttpConnectionManager_emptyFilters() throws ResourceInvalidExce thrown.expectMessage("Missing HttpFilter in HttpConnectionManager."); XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1560,7 +1725,7 @@ public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); VirtualHost virtualHost = Iterables.getOnlyElement(parsedHcm.virtualHosts()); Route parsedRoute = Iterables.getOnlyElement(virtualHost.routes()); @@ -1640,7 +1805,7 @@ public void parseHttpConnectionManager_duplicatePluginName() throws Exception { XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -1692,7 +1857,7 @@ public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { XdsListenerResource.parseHttpConnectionManager( hcm, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @@ -1766,7 +1931,7 @@ public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidEx HttpFilter.newBuilder().setName("terminal").setTypedConfig( Any.pack(Router.newBuilder().build())).setIsOptional(true)) .build(), filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); // Verify that the only route left is the one with the registered RLS plugin `rls-plugin-1`, // while the route with unregistered optional `optional-plugin-`1 has been skipped. @@ -1794,7 +1959,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio .build(); XdsListenerResource.parseHttpConnectionManager( hcm1, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); HttpConnectionManager hcm2 = HttpConnectionManager.newBuilder() @@ -1808,7 +1973,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio .build(); XdsListenerResource.parseHttpConnectionManager( hcm2, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); HttpConnectionManager hcm3 = HttpConnectionManager.newBuilder() @@ -1826,7 +1991,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); XdsListenerResource.parseHttpConnectionManager( hcm3, filterRegistry, - true /* does not matter */); + true /* does not matter */, getXdsResourceTypeArgs(true)); } @Test @@ -2184,7 +2349,7 @@ public void parseServerSideListener_invalidTrafficDirection() throws ResourceInv thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 with invalid traffic direction: OUTBOUND"); XdsListenerResource.parseServerSideListener( - listener, null, filterRegistry, null); + listener, null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2194,7 +2359,7 @@ public void parseServerSideListener_noTrafficDirection() throws ResourceInvalidE .setName("listener1") .build(); XdsListenerResource.parseServerSideListener( - listener, null, filterRegistry, null); + listener, null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2208,7 +2373,7 @@ public void parseServerSideListener_listenerFiltersPresent() throws ResourceInva thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have listener_filters"); XdsListenerResource.parseServerSideListener( - listener, null, filterRegistry, null); + listener, null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2222,7 +2387,7 @@ public void parseServerSideListener_useOriginalDst() throws ResourceInvalidExcep thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have use_original_dst set to true"); XdsListenerResource.parseServerSideListener( - listener,null, filterRegistry, null); + listener,null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2271,7 +2436,7 @@ public void parseServerSideListener_nonUniqueFilterChainMatch() throws ResourceI thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); XdsListenerResource.parseServerSideListener( - listener, null, filterRegistry, null); + listener, null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2320,7 +2485,7 @@ public void parseServerSideListener_nonUniqueFilterChainMatch_sameFilter() thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); XdsListenerResource.parseServerSideListener( - listener,null, filterRegistry, null); + listener,null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2369,7 +2534,7 @@ public void parseServerSideListener_uniqueFilterChainMatch() throws ResourceInva .addAllFilterChains(Arrays.asList(filterChain1, filterChain2)) .build(); XdsListenerResource.parseServerSideListener( - listener, null, filterRegistry, null); + listener, null, filterRegistry, null, getXdsResourceTypeArgs(true)); } @Test @@ -2384,7 +2549,8 @@ public void parseFilterChain_noHcm() throws ResourceInvalidException { thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); XdsListenerResource.parseFilterChain( - filterChain, null, filterRegistry, null, null); + filterChain, null, filterRegistry, null, null, + getXdsResourceTypeArgs(true)); } @Test @@ -2402,7 +2568,8 @@ public void parseFilterChain_duplicateFilter() throws ResourceInvalidException { thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); XdsListenerResource.parseFilterChain( - filterChain, null, filterRegistry, null, null); + filterChain, null, filterRegistry, null, null, + getXdsResourceTypeArgs(true)); } @Test @@ -2420,7 +2587,8 @@ public void parseFilterChain_filterMissingTypedConfig() throws ResourceInvalidEx "FilterChain filter-chain-foo contains filter envoy.http_connection_manager " + "without typed_config"); XdsListenerResource.parseFilterChain( - filterChain, null, filterRegistry, null, null); + filterChain, null, filterRegistry, null, null, + getXdsResourceTypeArgs(true)); } @Test @@ -2442,7 +2610,8 @@ public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException "FilterChain filter-chain-foo contains filter unsupported with unsupported " + "typed_config type unsupported-type-url"); XdsListenerResource.parseFilterChain( - filterChain, null, filterRegistry, null, null); + filterChain, null, filterRegistry, null, null, + getXdsResourceTypeArgs(true)); } @Test @@ -2470,10 +2639,10 @@ public void parseFilterChain_noName() throws ResourceInvalidException { EnvoyServerProtoData.FilterChain parsedFilterChain1 = XdsListenerResource.parseFilterChain( filterChain1, null, filterRegistry, null, - null); + null, getXdsResourceTypeArgs(true)); EnvoyServerProtoData.FilterChain parsedFilterChain2 = XdsListenerResource.parseFilterChain( filterChain2, null, filterRegistry, null, - null); + null, getXdsResourceTypeArgs(true)); assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name()); } @@ -2957,4 +3126,10 @@ private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters "type.googleapis.com")) .build(); } + + private XdsResourceType.Args getXdsResourceTypeArgs(boolean isTrustedServer) { + return new XdsResourceType.Args( + ServerInfo.create("http://td", "", false, isTrustedServer), "1.0", null, null, null, null + ); + } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index a9fda599183..cc704d04cc6 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -249,7 +249,7 @@ public long currentTimeNanos() { // EDS test resources. private final Message lbEndpointHealthy = mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", - mf.buildLbEndpoint("192.168.0.1", 8080, "healthy", 2), 1, 0); + mf.buildLbEndpoint("192.168.0.1", 8080, "healthy", 2, "endpoint-host-name"), 1, 0); // Locality with 0 endpoints private final Message lbEndpointEmpty = mf.buildLocalityLbEndpoints("region3", "zone3", "subzone3", @@ -257,7 +257,7 @@ public long currentTimeNanos() { // Locality with 0-weight endpoint private final Message lbEndpointZeroWeight = mf.buildLocalityLbEndpoints("region4", "zone4", "subzone4", - mf.buildLbEndpoint("192.168.142.5", 80, "unknown", 5), 0, 2); + mf.buildLbEndpoint("192.168.142.5", 80, "unknown", 5, "endpoint-host-name"), 0, 2); private final Any testClusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, ImmutableList.of(lbEndpointHealthy, lbEndpointEmpty, lbEndpointZeroWeight), ImmutableList.of(mf.buildDropOverload("lb", 200), mf.buildDropOverload("throttle", 1000)))); @@ -340,7 +340,8 @@ public XdsTransport create(ServerInfo serverInfo) { } }; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion()); + xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), + true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -595,7 +596,8 @@ private void validateGoldenClusterLoadAssignment(EdsUpdate edsUpdate) { .containsExactly( Locality.create("region1", "zone1", "subzone1"), LocalityLbEndpoints.create( - ImmutableList.of(LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0), + ImmutableList.of(LbEndpoint.create("192.168.0.1", 8080, 2, true, + "endpoint-host-name")), 1, 0), Locality.create("region3", "zone3", "subzone3"), LocalityLbEndpoints.create(ImmutableList.of(), 2, 1)); } @@ -1134,7 +1136,7 @@ public void edsResourceUpdated_withXdstpResourceName_withWrongType() { edsResourceNameWithWrongType, ImmutableList.of(mf.buildLocalityLbEndpoints( "region2", "zone2", "subzone2", - mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3), 2, 0)), + mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3, "endpoint-host-name"), 2, 0)), ImmutableList.of())); call.sendResponse(EDS, testEdsConfig, VERSION_1, "0000"); call.verifyRequestNack( @@ -3048,7 +3050,7 @@ public void simpleFlowControl() throws Exception { // Updated EDS response. Any updatedClusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, ImmutableList.of(mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", - mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3), 2, 0)), + mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3, "endpoint-host-name"), 2, 0)), ImmutableList.of())); call.sendResponse(EDS, updatedClusterLoadAssignment, VERSION_2, "0001"); // message not processed due to flow control @@ -3109,7 +3111,7 @@ public void edsResourceUpdated() { // Updated EDS response. Any updatedClusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, ImmutableList.of(mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", - mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3), 2, 0)), + mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3, "endpoint-host-name"), 2, 0)), ImmutableList.of())); call.sendResponse(EDS, updatedClusterLoadAssignment, VERSION_2, "0001"); @@ -3122,7 +3124,7 @@ public void edsResourceUpdated() { Locality.create("region2", "zone2", "subzone2"), LocalityLbEndpoints.create( ImmutableList.of( - LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0)); + LbEndpoint.create("172.44.2.2", 8000, 3, true, "endpoint-host-name")), 2, 0)); verifyResourceMetadataAcked(EDS, EDS_RESOURCE, updatedClusterLoadAssignment, VERSION_2, TIME_INCREMENT * 2); verifySubscribedResourcesMetadataSizes(0, 0, 0, 1); @@ -3138,9 +3140,9 @@ public void edsDuplicateLocalityInTheSamePriority() { Any updatedClusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, ImmutableList.of( mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", - mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3), 2, 1), + mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3, "endpoint-host-name"), 2, 1), mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", - mf.buildLbEndpoint("172.44.2.3", 8080, "healthy", 10), 2, 1) + mf.buildLbEndpoint("172.44.2.3", 8080, "healthy", 10, "endpoint-host-name"), 2, 1) ), ImmutableList.of())); call.sendResponse(EDS, updatedClusterLoadAssignment, "0", "0001"); @@ -3201,7 +3203,8 @@ public void edsResourceDeletedByCds() { mf.buildClusterLoadAssignment(resource, ImmutableList.of( mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", - mf.buildLbEndpoint("192.168.0.2", 9090, "healthy", 3), 1, 0)), + mf.buildLbEndpoint("192.168.0.2", 9090, "healthy", 3, + "endpoint-host-name"), 1, 0)), ImmutableList.of(mf.buildDropOverload("lb", 100))))); call.sendResponse(EDS, clusterLoadAssignments, VERSION_1, "0000"); verify(edsWatcher).onChanged(edsUpdateCaptor.capture()); @@ -3278,7 +3281,8 @@ public void multipleEdsWatchers() { mf.buildClusterLoadAssignment(edsResourceTwo, ImmutableList.of( mf.buildLocalityLbEndpoints("region2", "zone2", "subzone2", - mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 3), 2, 0)), + mf.buildLbEndpoint("172.44.2.2", 8000, "healthy", 3, "endpoint-host-name"), + 2, 0)), ImmutableList.of())); call.sendResponse(EDS, clusterLoadAssignmentTwo, VERSION_2, "0001"); @@ -3291,7 +3295,7 @@ public void multipleEdsWatchers() { Locality.create("region2", "zone2", "subzone2"), LocalityLbEndpoints.create( ImmutableList.of( - LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0)); + LbEndpoint.create("172.44.2.2", 8000, 3, true, "endpoint-host-name")), 2, 0)); verify(watcher2).onChanged(edsUpdateCaptor.capture()); edsUpdate = edsUpdateCaptor.getValue(); assertThat(edsUpdate.clusterName).isEqualTo(edsResourceTwo); @@ -3301,7 +3305,7 @@ public void multipleEdsWatchers() { Locality.create("region2", "zone2", "subzone2"), LocalityLbEndpoints.create( ImmutableList.of( - LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0)); + LbEndpoint.create("172.44.2.2", 8000, 3, true, "endpoint-host-name")), 2, 0)); verifyNoMoreInteractions(edsResourceWatcher); verifyResourceMetadataAcked( EDS, edsResourceTwo, clusterLoadAssignmentTwo, VERSION_2, TIME_INCREMENT * 2); @@ -3793,7 +3797,7 @@ private XdsClientImpl createXdsClient(String serverUri) { private BootstrapInfo buildBootStrap(String serverUri) { ServerInfo xdsServerInfo = ServerInfo.create(serverUri, CHANNEL_CREDENTIALS, - ignoreResourceDeletion()); + ignoreResourceDeletion(), true); return Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -4012,7 +4016,7 @@ protected Message buildLocalityLbEndpoints(String region, String zone, String su } protected abstract Message buildLbEndpoint(String address, int port, String healthStatus, - int lbWeight); + int lbWeight, String endpointHostname); protected abstract Message buildDropOverload(String category, int dropPerMillion); diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java index 40a9bff514f..af34c7232d0 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java @@ -705,7 +705,7 @@ protected Message buildLocalityLbEndpoints(String region, String zone, String su @Override protected Message buildLbEndpoint(String address, int port, String healthStatus, - int lbWeight) { + int lbWeight, String endpointHostname) { HealthStatus status; switch (healthStatus) { case "unknown": @@ -733,7 +733,8 @@ protected Message buildLbEndpoint(String address, int port, String healthStatus, .setEndpoint( Endpoint.newBuilder().setAddress( Address.newBuilder().setSocketAddress( - SocketAddress.newBuilder().setAddress(address).setPortValue(port)))) + SocketAddress.newBuilder().setAddress(address).setPortValue(port))) + .setHostname(endpointHostname)) .setHealthStatus(status) .setLoadBalancingWeight(UInt32Value.of(lbWeight)) .build(); diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 76b92cd8c03..f32c198f21f 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -318,13 +318,13 @@ public void resolving_targetAuthorityInAuthoritiesMap() { String serviceAuthority = "[::FFFF:129.144.52.38]:80"; bootstrapInfo = BootstrapInfo.builder() .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), true))) + "td.googleapis.com", InsecureChannelCredentials.create(), true, true))) .node(Node.newBuilder().build()) .authorities( ImmutableMap.of(targetAuthority, AuthorityInfo.create( "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s?foo=1&bar=2", ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), true))))) + "td.googleapis.com", InsecureChannelCredentials.create(), true, true))))) .build(); expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified @@ -348,11 +348,11 @@ public void resolving_ldsResourceNotFound() { public void resolving_ldsResourceUpdateRdsName() { Route route1 = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); Route route2 = Route.forAction(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null, false), ImmutableMap.of()); bootstrapInfo = BootstrapInfo.builder() .servers(ImmutableList.of(ServerInfo.create( @@ -418,7 +418,7 @@ public void resolving_rdsResourceNotFound() { public void resolving_ldsResourceRevokedAndAddedBack() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); resolver.start(mockListener); @@ -457,7 +457,7 @@ public void resolving_ldsResourceRevokedAndAddedBack() { public void resolving_rdsResourceRevokedAndAddedBack() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); resolver.start(mockListener); @@ -534,7 +534,7 @@ public void resolving_encounterErrorLdsAndRdsWatchers() { public void resolving_matchingVirtualHostNotFound_matchingOverrideAuthority() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList("random"), @@ -557,7 +557,7 @@ public void resolving_matchingVirtualHostNotFound_matchingOverrideAuthority() { public void resolving_matchingVirtualHostNotFound_notMatchingOverrideAuthority() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), @@ -604,11 +604,11 @@ public void resolving_matchingVirtualHostNotFoundInRdsResource() { private List buildUnmatchedVirtualHosts() { Route route1 = Route.forAction(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); Route route2 = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); return Arrays.asList( VirtualHost.create("virtualhost-foo", Collections.singletonList("hello.googleapis.com"), @@ -625,7 +625,7 @@ public void resolved_noTimeout() { FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), null, null), // per-route timeout unset + cluster1, Collections.emptyList(), null, null, false), // per-route timeout unset ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("does not matter", Collections.singletonList(AUTHORITY), Collections.singletonList(route), @@ -643,7 +643,7 @@ public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() { FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), null, null), // per-route timeout unset + cluster1, Collections.emptyList(), null, null, false), // per-route timeout unset ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("does not matter", Collections.singletonList(AUTHORITY), Collections.singletonList(route), @@ -675,7 +675,8 @@ public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() { cluster1, Collections.emptyList(), null, - retryPolicy), + retryPolicy, + false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -733,7 +734,7 @@ public void resolved_simpleCallFailedToRoute_routeWithNonForwardingAction() { Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster(cluster2, Collections.emptyList(), - TimeUnit.SECONDS.toNanos(15L), null), + TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -769,7 +770,8 @@ public void resolved_rpcHashingByHeader_withoutSubstitution() { Collections.singletonList( HashPolicy.forHeader(false, "custom-key", null, null)), null, - null), + null, + false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); InternalConfigSelector configSelector = @@ -801,9 +803,11 @@ public void resolved_rpcHashingByHeader_withSubstitution() { RouteAction.forCluster( cluster1, Collections.singletonList( - HashPolicy.forHeader(false, "custom-key", Pattern.compile("value"), "val")), + HashPolicy.forHeader(false, "custom-key", Pattern.compile("value"), + "val")), + null, null, - null), + false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); InternalConfigSelector configSelector = @@ -842,7 +846,8 @@ public void resolved_rpcHashingByChannelId() { cluster1, Collections.singletonList(HashPolicy.forChannelId(false)), null, - null), + null, + false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); InternalConfigSelector configSelector = @@ -878,7 +883,8 @@ public void resolved_rpcHashingByChannelId() { cluster1, Collections.singletonList(HashPolicy.forChannelId(false)), null, - null), + null, + false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); configSelector = resolutionResultCaptor.getValue().getAttributes().get( @@ -894,6 +900,68 @@ public void resolved_rpcHashingByChannelId() { assertThat(hash3).isNotEqualTo(hash1); } + @Test + public void resolved_routeActionHasAutoHostRewrite_emitsCallOptionForTheSame() { + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, serviceConfigParser, + syncContext, scheduler, xdsClientPoolFactory, mockRandom, + FilterRegistry.getDefaultRegistry(), null); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdate( + Collections.singletonList( + Route.forAction( + RouteMatch.withPathExactOnly( + "/" + TestMethodDescriptors.voidMethod().getFullMethodName()), + RouteAction.forCluster( + cluster1, + Collections.singletonList( + HashPolicy.forHeader(false, "custom-key", null, null)), + null, + null, + true), + ImmutableMap.of()))); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + InternalConfigSelector configSelector = + resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY); + + // First call, with header "custom-key": "custom-value". + startNewCall(TestMethodDescriptors.voidMethod(), configSelector, + ImmutableMap.of("custom-key", "custom-value"), CallOptions.DEFAULT); + + assertThat(testCall.callOptions.getOption(XdsNameResolver.AUTO_HOST_REWRITE_KEY)).isTrue(); + } + + @Test + public void resolved_routeActionNoAutoHostRewrite_doesntEmitCallOptionForTheSame() { + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, serviceConfigParser, + syncContext, scheduler, xdsClientPoolFactory, mockRandom, + FilterRegistry.getDefaultRegistry(), null); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdate( + Collections.singletonList( + Route.forAction( + RouteMatch.withPathExactOnly( + "/" + TestMethodDescriptors.voidMethod().getFullMethodName()), + RouteAction.forCluster( + cluster1, + Collections.singletonList( + HashPolicy.forHeader(false, "custom-key", null, null)), + null, + null, + false), + ImmutableMap.of()))); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + InternalConfigSelector configSelector = + resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY); + + // First call, with header "custom-key": "custom-value". + startNewCall(TestMethodDescriptors.voidMethod(), configSelector, + ImmutableMap.of("custom-key", "custom-value"), CallOptions.DEFAULT); + + assertThat(testCall.callOptions.getOption(XdsNameResolver.AUTO_HOST_REWRITE_KEY)).isNull(); + } + @SuppressWarnings("unchecked") @Test public void resolved_resourceUpdateAfterCallStarted() { @@ -909,13 +977,13 @@ public void resolved_resourceUpdateAfterCallStarted() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( "another-cluster", Collections.emptyList(), - TimeUnit.SECONDS.toNanos(20L), null), + TimeUnit.SECONDS.toNanos(20L), null, false), ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -949,13 +1017,13 @@ public void resolved_resourceUpdatedBeforeCallStarted() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( "another-cluster", Collections.emptyList(), - TimeUnit.SECONDS.toNanos(20L), null), + TimeUnit.SECONDS.toNanos(20L), null, false), ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()))); // Two consecutive service config updates: one for removing clcuster1, // one for adding "another=cluster". @@ -985,13 +1053,13 @@ public void resolved_raceBetweenCallAndRepeatedResourceUpdate() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( "another-cluster", Collections.emptyList(), - TimeUnit.SECONDS.toNanos(20L), null), + TimeUnit.SECONDS.toNanos(20L), null, false), ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), - TimeUnit.SECONDS.toNanos(15L), null), + TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); @@ -1006,13 +1074,13 @@ public void resolved_raceBetweenCallAndRepeatedResourceUpdate() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( "another-cluster", Collections.emptyList(), - TimeUnit.SECONDS.toNanos(15L), null), + TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), - TimeUnit.SECONDS.toNanos(15L), null), + TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()))); verifyNoMoreInteractions(mockListener); // no cluster added/deleted assertCallSelectClusterResult(call1, configSelector, "another-cluster", 15.0); @@ -1029,7 +1097,7 @@ public void resolved_raceBetweenClusterReleasedAndResourceUpdateAddBackAgain() { RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()))); xdsClient.deliverLdsUpdate( Arrays.asList( @@ -1037,13 +1105,13 @@ public void resolved_raceBetweenClusterReleasedAndResourceUpdateAddBackAgain() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()))); testCall.deliverErrorStatus(); verifyNoMoreInteractions(mockListener); @@ -1067,7 +1135,7 @@ public void resolved_simpleCallSucceeds_routeToWeightedCluster() { cluster2, 80, ImmutableMap.of())), Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), - null), + null, false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -1096,7 +1164,7 @@ public void resolved_simpleCallSucceeds_routeToRls() { ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"))), Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), - null), + null, false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -1144,7 +1212,7 @@ public void resolved_simpleCallSucceeds_routeToRls() { Collections.emptyList(), // changed TimeUnit.SECONDS.toNanos(30L), - null), + null, false), ImmutableMap.of()))); verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); ResolutionResult result2 = resolutionResultCaptor.getValue(); @@ -1250,13 +1318,13 @@ private InternalConfigSelector resolveToClusters() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -1305,7 +1373,7 @@ public void generateServiceConfig_forClusterManagerLoadBalancingConfig() throws Route route1 = Route.forAction( RouteMatch.withPathExactOnly("HelloService/hi"), RouteAction.forCluster( - "cluster-foo", Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + "cluster-foo", Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null, false), ImmutableMap.of()); Route route2 = Route.forAction( RouteMatch.withPathExactOnly("HelloService/hello"), @@ -1315,7 +1383,7 @@ public void generateServiceConfig_forClusterManagerLoadBalancingConfig() throws ClusterWeight.create("cluster-baz", 50, ImmutableMap.of())), ImmutableList.of(), TimeUnit.SECONDS.toNanos(15L), - null), + null, false), ImmutableMap.of()); Map rlsConfig = ImmutableMap.of("lookupService", "rls.bigtable.google.com"); Route route3 = Route.forAction( @@ -1324,7 +1392,7 @@ public void generateServiceConfig_forClusterManagerLoadBalancingConfig() throws NamedPluginConfig.create("plugin-foo", RlsPluginConfig.create(rlsConfig)), Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), - null), + null, false), ImmutableMap.of()); resolver.start(mockListener); @@ -1914,6 +1982,7 @@ private PickSubchannelArgs newPickSubchannelArgs( private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { Set targets = new HashSet<>(); + XdsClient xdsClient = new FakeXdsClient(); @Override public void setBootstrapOverride(Map bootstrap) {} @@ -1930,7 +1999,7 @@ public ObjectPool getOrCreate(String target) throws XdsInitialization return new ObjectPool() { @Override public XdsClient getObject() { - return new FakeXdsClient(); + return xdsClient; } @Override @@ -2058,7 +2127,8 @@ void deliverLdsUpdateWithFaultInjection( Collections.singletonList(clusterWeight), Collections.emptyList(), null, - null), + null, + false), overrideConfig); overrideConfig = virtualHostFaultConfig == null ? ImmutableMap.of() @@ -2125,7 +2195,8 @@ void deliverRdsUpdateWithFaultInjection( Collections.singletonList(clusterWeight), Collections.emptyList(), null, - null), + null, + false), overrideConfig); overrideConfig = virtualHostFaultConfig == null ? ImmutableMap.of() diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index 55b8812cd17..66ac1475d8e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -1035,7 +1035,8 @@ public void run() { "/FooService/barMethod", "foo.google.com", Route.RouteAction.forCluster( - "cluster", Collections.emptyList(), null, null)); + "cluster", Collections.emptyList(), null, null, + false)); ServerCall serverCall = mock(ServerCall.class); when(serverCall.getAttributes()).thenReturn( Attributes.newBuilder()