From 872b0cc24adad6f6ec4c982ddfbe3c2bf997d7f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:53:02 +0000 Subject: [PATCH 1/3] Initial plan From 106e03d21f23962d7f743910ba26bdf3a88a43d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:06:53 +0000 Subject: [PATCH 2/3] Fix Java 8 compatibility: replace pattern matching instanceof in XdsServerBuilder, add ldsResourceNameResolver support --- .../java/io/grpc/xds/XdsServerBuilder.java | 43 ++++- .../java/io/grpc/xds/XdsServerWrapper.java | 172 ++++++++++++++---- .../io/grpc/xds/XdsServerWrapperTest.java | 45 ++++- 3 files changed, 226 insertions(+), 34 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsServerBuilder.java b/xds/src/main/java/io/grpc/xds/XdsServerBuilder.java index 1c0eb3cd024..a7427a1a4ad 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerBuilder.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerBuilder.java @@ -37,10 +37,14 @@ import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.NettyServerBuilder; import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingNegotiatorServerFactory; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import java.util.logging.Logger; +import javax.annotation.Nullable; /** * A version of {@link ServerBuilder} to create xDS managed servers. @@ -57,6 +61,7 @@ public final class XdsServerBuilder extends ForwardingServerBuilder bootstrapOverride; + @Nullable private Function ldsResourceNameResolver; private long drainGraceTime = 10; private TimeUnit drainGraceTimeUnit = TimeUnit.MINUTES; private ChannelConfigurator channelConfigurator = builder -> { }; @@ -134,6 +139,23 @@ public static XdsServerBuilder forPort(int port, ServerCredentials serverCredent return new XdsServerBuilder(nettyDelegate, port); } + /** Creates a gRPC server builder for the given address. */ + public static XdsServerBuilder forAddress( + SocketAddress address, ServerCredentials serverCredentials) { + checkNotNull(serverCredentials, "serverCredentials"); + InternalProtocolNegotiator.ServerFactory originalNegotiatorFactory = + InternalNettyServerCredentials.toNegotiator(serverCredentials); + ServerCredentials wrappedCredentials = + InternalNettyServerCredentials.create( + new FilterChainMatchingNegotiatorServerFactory(originalNegotiatorFactory)); + NettyServerBuilder nettyDelegate = NettyServerBuilder.forAddress(address, wrappedCredentials); + int port = 0; + if (address instanceof InetSocketAddress) { + port = ((InetSocketAddress) address).getPort(); + } + return new XdsServerBuilder(nettyDelegate, port); + } + @Override public Server build() { checkState(isServerBuilt.compareAndSet(false, true), "Server already built!"); @@ -144,11 +166,28 @@ public Server build() { builder.set(ATTR_DRAIN_GRACE_NANOS, drainGraceTimeUnit.toNanos(drainGraceTime)); } InternalNettyServerBuilder.eagAttributes(delegate, builder.build()); - return new XdsServerWrapper("0.0.0.0:" + port, delegate, xdsServingStatusListener, - filterChainSelectorManager, xdsClientPoolFactory, bootstrapOverride, filterRegistry, + return new XdsServerWrapper( + "0.0.0.0:" + port, + delegate, + xdsServingStatusListener, + filterChainSelectorManager, + xdsClientPoolFactory, + bootstrapOverride, + ldsResourceNameResolver, + filterRegistry, this.channelConfigurator); } + /** + * Provides a function that takes the listening address and returns the LDS resource name. When + * provided, this overrides the server_listener_resource_name_template in the bootstrap. + */ + public XdsServerBuilder ldsResourceNameResolver( + Function ldsResourceNameResolver) { + this.ldsResourceNameResolver = checkNotNull(ldsResourceNameResolver, "ldsResourceNameResolver"); + return this; + } + @VisibleForTesting XdsServerBuilder xdsClientPoolFactory(XdsClientPoolFactory xdsClientPoolFactory) { this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory"); diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index 8aeae7f4988..af17ba9e2ea 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -78,6 +78,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -108,6 +109,7 @@ public void uncaughtException(Thread t, Throwable e) { private final ThreadSafeRandom random = ThreadSafeRandomImpl.instance; private final XdsClientPoolFactory xdsClientPoolFactory; private final @Nullable Map bootstrapOverride; + private final @Nullable Function ldsResourceNameResolver; private final XdsServingStatusListener listener; private final FilterChainSelectorManager filterChainSelectorManager; private final AtomicBoolean started = new AtomicBoolean(false); @@ -139,6 +141,7 @@ public void uncaughtException(Thread t, Throwable e) { FilterChainSelectorManager filterChainSelectorManager, XdsClientPoolFactory xdsClientPoolFactory, @Nullable Map bootstrapOverride, + @Nullable Function ldsResourceNameResolver, FilterRegistry filterRegistry, ChannelConfigurator channelConfigurator) { this( @@ -148,12 +151,59 @@ public void uncaughtException(Thread t, Throwable e) { filterChainSelectorManager, xdsClientPoolFactory, bootstrapOverride, + ldsResourceNameResolver, filterRegistry, SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE), channelConfigurator); sharedTimeService = true; } + XdsServerWrapper( + String listenerAddress, + ServerBuilder delegateBuilder, + XdsServingStatusListener listener, + FilterChainSelectorManager filterChainSelectorManager, + XdsClientPoolFactory xdsClientPoolFactory, + @Nullable Map bootstrapOverride, + FilterRegistry filterRegistry, + ChannelConfigurator channelConfigurator) { + this( + listenerAddress, + delegateBuilder, + listener, + filterChainSelectorManager, + xdsClientPoolFactory, + bootstrapOverride, + null, + filterRegistry, + SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE), + channelConfigurator); + sharedTimeService = true; + } + + XdsServerWrapper( + String listenerAddress, + ServerBuilder delegateBuilder, + XdsServingStatusListener listener, + FilterChainSelectorManager filterChainSelectorManager, + XdsClientPoolFactory xdsClientPoolFactory, + @Nullable Map bootstrapOverride, + @Nullable Function ldsResourceNameResolver, + FilterRegistry filterRegistry) { + this( + listenerAddress, + delegateBuilder, + listener, + filterChainSelectorManager, + xdsClientPoolFactory, + bootstrapOverride, + ldsResourceNameResolver, + filterRegistry, + SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE), + builder -> { }); + sharedTimeService = true; + } + XdsServerWrapper( String listenerAddress, ServerBuilder delegateBuilder, @@ -169,20 +219,24 @@ public void uncaughtException(Thread t, Throwable e) { filterChainSelectorManager, xdsClientPoolFactory, bootstrapOverride, + null, filterRegistry, + SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE), builder -> { }); + sharedTimeService = true; } @VisibleForTesting XdsServerWrapper( - String listenerAddress, - ServerBuilder delegateBuilder, - XdsServingStatusListener listener, - FilterChainSelectorManager filterChainSelectorManager, - XdsClientPoolFactory xdsClientPoolFactory, - @Nullable Map bootstrapOverride, - FilterRegistry filterRegistry, - ScheduledExecutorService timeService) { + String listenerAddress, + ServerBuilder delegateBuilder, + XdsServingStatusListener listener, + FilterChainSelectorManager filterChainSelectorManager, + XdsClientPoolFactory xdsClientPoolFactory, + @Nullable Map bootstrapOverride, + @Nullable Function ldsResourceNameResolver, + FilterRegistry filterRegistry, + ScheduledExecutorService timeService) { this( listenerAddress, delegateBuilder, @@ -190,6 +244,7 @@ public void uncaughtException(Thread t, Throwable e) { filterChainSelectorManager, xdsClientPoolFactory, bootstrapOverride, + ldsResourceNameResolver, filterRegistry, timeService, builder -> { }); @@ -197,15 +252,63 @@ public void uncaughtException(Thread t, Throwable e) { @VisibleForTesting XdsServerWrapper( - String listenerAddress, - ServerBuilder delegateBuilder, - XdsServingStatusListener listener, - FilterChainSelectorManager filterChainSelectorManager, - XdsClientPoolFactory xdsClientPoolFactory, - @Nullable Map bootstrapOverride, - FilterRegistry filterRegistry, - ScheduledExecutorService timeService, - ChannelConfigurator channelConfigurator) { + String listenerAddress, + ServerBuilder delegateBuilder, + XdsServingStatusListener listener, + FilterChainSelectorManager filterChainSelectorManager, + XdsClientPoolFactory xdsClientPoolFactory, + @Nullable Map bootstrapOverride, + FilterRegistry filterRegistry, + ScheduledExecutorService timeService) { + this( + listenerAddress, + delegateBuilder, + listener, + filterChainSelectorManager, + xdsClientPoolFactory, + bootstrapOverride, + null, + filterRegistry, + timeService, + builder -> { }); + } + + @VisibleForTesting + XdsServerWrapper( + String listenerAddress, + ServerBuilder delegateBuilder, + XdsServingStatusListener listener, + FilterChainSelectorManager filterChainSelectorManager, + XdsClientPoolFactory xdsClientPoolFactory, + @Nullable Map bootstrapOverride, + FilterRegistry filterRegistry, + ScheduledExecutorService timeService, + ChannelConfigurator channelConfigurator) { + this( + listenerAddress, + delegateBuilder, + listener, + filterChainSelectorManager, + xdsClientPoolFactory, + bootstrapOverride, + null, + filterRegistry, + timeService, + channelConfigurator); + } + + @VisibleForTesting + XdsServerWrapper( + String listenerAddress, + ServerBuilder delegateBuilder, + XdsServingStatusListener listener, + FilterChainSelectorManager filterChainSelectorManager, + XdsClientPoolFactory xdsClientPoolFactory, + @Nullable Map bootstrapOverride, + @Nullable Function ldsResourceNameResolver, + FilterRegistry filterRegistry, + ScheduledExecutorService timeService, + ChannelConfigurator channelConfigurator) { this.listenerAddress = checkNotNull(listenerAddress, "listenerAddress"); this.delegateBuilder = checkNotNull(delegateBuilder, "delegateBuilder"); this.delegateBuilder.intercept(new ConfigApplyingInterceptor()); @@ -214,6 +317,7 @@ public void uncaughtException(Thread t, Throwable e) { = checkNotNull(filterChainSelectorManager, "filterChainSelectorManager"); this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory"); this.bootstrapOverride = bootstrapOverride; + this.ldsResourceNameResolver = ldsResourceNameResolver; this.timeService = checkNotNull(timeService, "timeService"); this.filterRegistry = checkNotNull(filterRegistry,"filterRegistry"); this.delegate = delegateBuilder.build(); @@ -261,21 +365,27 @@ private void internalStart() { return; } xdsClient = xdsClientPool.getObject(); - String listenerTemplate = xdsClient.getBootstrapInfo().serverListenerResourceNameTemplate(); - if (listenerTemplate == null) { - StatusException statusException = - Status.UNAVAILABLE.withDescription( - "Can only support xDS v3 with listener resource name template").asException(); - listener.onNotServing(statusException); - initialStartFuture.set(statusException); - xdsClient = xdsClientPool.returnObject(xdsClient); - return; - } - String replacement = listenerAddress; - if (listenerTemplate.startsWith(XDSTP_SCHEME)) { - replacement = XdsClient.percentEncodePath(replacement); + String resourceName; + if (ldsResourceNameResolver != null) { + resourceName = ldsResourceNameResolver.apply(listenerAddress); + } else { + String listenerTemplate = xdsClient.getBootstrapInfo().serverListenerResourceNameTemplate(); + if (listenerTemplate == null) { + StatusException statusException = + Status.UNAVAILABLE.withDescription( + "Can only support xDS v3 with listener resource name template").asException(); + listener.onNotServing(statusException); + initialStartFuture.set(statusException); + xdsClient = xdsClientPool.returnObject(xdsClient); + return; + } + String replacement = listenerAddress; + if (listenerTemplate.startsWith(XDSTP_SCHEME)) { + replacement = XdsClient.percentEncodePath(replacement); + } + resourceName = listenerTemplate.replaceAll("%s", replacement); } - discoveryState = new DiscoveryState(listenerTemplate.replaceAll("%s", replacement)); + discoveryState = new DiscoveryState(resourceName); } @Override diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index cc26195b039..93fec4d538f 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -257,7 +257,50 @@ public void run() { } @Test - public void shutdown() throws Exception { + @SuppressWarnings("unchecked") + public void testBootstrap_ldsResourceNameResolver() throws Exception { + Bootstrapper.BootstrapInfo b = + Bootstrapper.BootstrapInfo.builder() + .servers( + Arrays.asList( + Bootstrapper.ServerInfo.create("uri", InsecureChannelCredentials.create()))) + .node(EnvoyProtoData.Node.newBuilder().setId("id").build()) + .serverListenerResourceNameTemplate("grpc/server?udpa.resource.listening_address=%s") + .build(); + XdsClient xdsClient = mock(XdsClient.class); + XdsListenerResource listenerResource = XdsListenerResource.getInstance(); + when(xdsClient.getBootstrapInfo()).thenReturn(b); + xdsServerWrapper = + new XdsServerWrapper( + "[::FFFF:129.144.52.38]:80", + mockBuilder, + listener, + selectorManager, + new FakeXdsClientPoolFactory(xdsClient), + XdsServerTestHelper.RAW_BOOTSTRAP, + addr -> "xdstp://resolved_name/" + addr, + filterRegistry); + Executors.newSingleThreadExecutor() + .execute( + new Runnable() { + @Override + public void run() { + try { + xdsServerWrapper.start(); + } catch (IOException ex) { + // ignore + } + } + }); + verify(xdsClient, timeout(5000)) + .watchXdsResource( + eq(listenerResource), + eq("xdstp://resolved_name/[::FFFF:129.144.52.38]:80"), + any(ResourceWatcher.class), + any(SynchronizationContext.class)); + } + + @Test final SettableFuture start = SettableFuture.create(); Executors.newSingleThreadExecutor().execute(new Runnable() { @Override From ca681bf4ba65b9a02d7af7dbc35b6865fa9c06eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:07:47 +0000 Subject: [PATCH 3/3] Address code review: rename test and improve exception comment --- xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index 93fec4d538f..9af66b5dad2 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -258,7 +258,7 @@ public void run() { @Test @SuppressWarnings("unchecked") - public void testBootstrap_ldsResourceNameResolver() throws Exception { + public void testLdsResourceNameResolver_overridesBootstrapTemplate() throws Exception { Bootstrapper.BootstrapInfo b = Bootstrapper.BootstrapInfo.builder() .servers( @@ -288,7 +288,7 @@ public void run() { try { xdsServerWrapper.start(); } catch (IOException ex) { - // ignore + // Ignored: test only asserts on the watchXdsResource call, not start() result. } } });