diff --git a/api/src/main/java/io/grpc/EquivalentAddressGroup.java b/api/src/main/java/io/grpc/EquivalentAddressGroup.java index bf8a864902c..1ffe9786217 100644 --- a/api/src/main/java/io/grpc/EquivalentAddressGroup.java +++ b/api/src/main/java/io/grpc/EquivalentAddressGroup.java @@ -34,6 +34,7 @@ */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770") public final class EquivalentAddressGroup { + static final int MAX_ADDRESSES_TO_STRING = 100; /** * The authority to be used when constructing Subchannels for this EquivalentAddressGroup. @@ -113,8 +114,24 @@ public Attributes getAttributes() { @Override public String toString() { - // TODO(zpencer): Summarize return value if addr is very large - return "[" + addrs + "/" + attrs + "]"; + StringBuilder sb = new StringBuilder(); + sb.append('['); + if (addrs.size() <= MAX_ADDRESSES_TO_STRING) { + sb.append(addrs); + } else { + sb.append('['); + for (int i = 0; i < MAX_ADDRESSES_TO_STRING; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(addrs.get(i)); + } + sb.append(", ... "); + sb.append(addrs.size() - MAX_ADDRESSES_TO_STRING); + sb.append(" more]"); + } + sb.append('/').append(attrs).append(']'); + return sb.toString(); } @Override diff --git a/api/src/test/java/io/grpc/EquivalentAddressGroupTest.java b/api/src/test/java/io/grpc/EquivalentAddressGroupTest.java new file mode 100644 index 00000000000..fab960589e3 --- /dev/null +++ b/api/src/test/java/io/grpc/EquivalentAddressGroupTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2026 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + +import static com.google.common.truth.Truth.assertThat; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link EquivalentAddressGroup}. + */ +@RunWith(JUnit4.class) +public class EquivalentAddressGroupTest { + + @Test + public void toString_summarizesLargeAddressList() { + int maxAddressesToString = EquivalentAddressGroup.MAX_ADDRESSES_TO_STRING; + List addrs = new ArrayList<>(); + for (int i = 0; i <= maxAddressesToString; i++) { + addrs.add(new FakeSocketAddress("addr" + i)); + } + EquivalentAddressGroup eag = new EquivalentAddressGroup(addrs); + + StringBuilder expected = new StringBuilder(); + expected.append('[').append('['); + for (int i = 0; i < maxAddressesToString; i++) { + if (i > 0) { + expected.append(", "); + } + expected.append(addrs.get(i)); + } + expected.append(", ... 1 more]/{}]"); + assertThat(eag.toString()).isEqualTo(expected.toString()); + } + + @Test + public void toString_doesNotSummarizeAtMaxAddressCount() { + int maxAddressesToString = EquivalentAddressGroup.MAX_ADDRESSES_TO_STRING; + List addrs = new ArrayList<>(); + for (int i = 0; i < maxAddressesToString; i++) { + addrs.add(new FakeSocketAddress("addr" + i)); + } + EquivalentAddressGroup eag = new EquivalentAddressGroup(addrs); + + String expected = "[" + addrs + "/{}]"; + assertThat(eag.toString()).isEqualTo(expected); + } + + private static final class FakeSocketAddress extends SocketAddress { + + private final String name; + + FakeSocketAddress(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java index 1aa11ecf9af..1aec3be5ba5 100644 --- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java +++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java @@ -46,6 +46,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -211,6 +212,40 @@ public ConfigOrError answer(InvocationOnMock invocation) { verify(mockResourceResolver).resolveSrv("_grpclb._tcp." + hostName); } + @Test + public void resolve_presentResourceResolver_manyBalancerAddressesSummarizedInToString() + throws Exception { + int addressCount = 1000; + InetAddress backendAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 0}); + String lbName = "foo.example.com."; // original name in SRV record + SrvRecord srvRecord = new SrvRecord(lbName, 8080); + List lbAddrs = new ArrayList<>(); + for (int i = 0; i < addressCount; i++) { + lbAddrs.add(InetAddress.getByAddress(new byte[] {10, 1, 0, (byte) (i + 1)})); + } + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(hostName)) + .thenReturn(Collections.singletonList(backendAddr)); + when(mockAddressResolver.resolveAddress(lbName)) + .thenReturn(lbAddrs); + ResourceResolver mockResourceResolver = mock(ResourceResolver.class); + when(mockResourceResolver.resolveTxt(anyString())).thenReturn(Collections.emptyList()); + when(mockResourceResolver.resolveSrv(anyString())) + .thenReturn(Collections.singletonList(srvRecord)); + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(mockResourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onResult2(resultCaptor.capture()); + ResolutionResult result = resultCaptor.getValue(); + EquivalentAddressGroup resolvedBalancerAddr = + Iterables.getOnlyElement(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)); + assertThat(resolvedBalancerAddr.getAddresses().size()).isEqualTo(addressCount); + assertThat(resolvedBalancerAddr.toString()).contains("... "); + } + @Test public void resolve_nullResourceResolver() throws Exception { InetAddress backendAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 0}); @@ -341,4 +376,5 @@ public void resolve_addressAndBalancersLookupFail_neverLookupServiceConfig() thr verify(mockResourceResolver, never()).resolveTxt("_grpc_config." + hostName); verify(mockResourceResolver).resolveSrv("_grpclb._tcp." + hostName); } + }