Skip to content

Commit 6eaae5d

Browse files
core/grpclb: resolve TXT records in DNS name resolver and include balancer addresses
1 parent 04420df commit 6eaae5d

File tree

7 files changed

+192
-50
lines changed

7 files changed

+192
-50
lines changed

core/src/main/java/io/grpc/internal/DnsNameResolver.java

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020

2121
import com.google.common.annotations.VisibleForTesting;
2222
import com.google.common.base.Preconditions;
23+
import com.google.common.base.Verify;
2324
import io.grpc.Attributes;
2425
import io.grpc.EquivalentAddressGroup;
2526
import io.grpc.NameResolver;
2627
import io.grpc.Status;
2728
import io.grpc.internal.SharedResourceHolder.Resource;
2829
import java.net.InetAddress;
2930
import java.net.InetSocketAddress;
31+
import java.net.SocketAddress;
3032
import java.net.URI;
33+
import java.net.UnknownHostException;
3134
import java.util.ArrayList;
3235
import java.util.Arrays;
3336
import java.util.Collections;
@@ -38,6 +41,7 @@
3841
import java.util.concurrent.TimeUnit;
3942
import java.util.logging.Level;
4043
import java.util.logging.Logger;
44+
import java.util.regex.Pattern;
4145
import javax.annotation.Nullable;
4246
import javax.annotation.concurrent.GuardedBy;
4347
import javax.naming.NamingEnumeration;
@@ -49,7 +53,7 @@
4953
* A DNS-based {@link NameResolver}.
5054
*
5155
* <p>Each {@code A} or {@code AAAA} record emits an {@link EquivalentAddressGroup} in the list
52-
* passed to {@link NameResolver.Listener#onUpdate}
56+
* passed to {@link NameResolver.Listener#onAddresses(List, Attributes)}
5357
*
5458
* @see DnsNameResolverProvider
5559
*/
@@ -59,8 +63,16 @@ final class DnsNameResolver extends NameResolver {
5963

6064
private static final boolean JNDI_AVAILABLE = jndiAvailable();
6165

66+
// From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
67+
private static final String SERVICE_CONFIG_NAME_PREFIX = "_grpc_config.";
68+
// From https://github.com/grpc/proposal/blob/master/A5-grpclb-in-dns.md
69+
private static final String GRPCLB_NAME_PREFIX = "_grpclb._tcp.";
70+
71+
private static final String jndiProperty =
72+
System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi", "false");
73+
6274
@VisibleForTesting
63-
static boolean enableJndi = false;
75+
static boolean enableJndi = Boolean.parseBoolean(jndiProperty);
6476

6577
private DelegateResolver delegateResolver = pickDelegateResolver();
6678

@@ -174,11 +186,19 @@ public void run() {
174186
return;
175187
}
176188
// Each address forms an EAG
177-
ArrayList<EquivalentAddressGroup> servers = new ArrayList<EquivalentAddressGroup>();
189+
List<EquivalentAddressGroup> servers = new ArrayList<EquivalentAddressGroup>();
178190
for (InetAddress inetAddr : resolvedInetAddrs.addresses) {
179191
servers.add(new EquivalentAddressGroup(new InetSocketAddress(inetAddr, port)));
180192
}
181-
savedListener.onAddresses(servers, Attributes.EMPTY);
193+
servers.addAll(resolvedInetAddrs.balancerAddresses);
194+
195+
Attributes.Builder attrs = Attributes.newBuilder();
196+
if (!resolvedInetAddrs.txtRecords.isEmpty()) {
197+
attrs.set(
198+
GrpcAttributes.NAME_RESOLVER_ATTR_DNS_TXT,
199+
Collections.unmodifiableList(new ArrayList<String>(resolvedInetAddrs.txtRecords)));
200+
}
201+
savedListener.onAddresses(servers, attrs.build());
182202
} finally {
183203
synchronized (DnsNameResolver.this) {
184204
resolving = false;
@@ -278,10 +298,16 @@ abstract static class DelegateResolver {
278298
static final class ResolutionResults {
279299
final List<InetAddress> addresses;
280300
final List<String> txtRecords;
301+
final List<EquivalentAddressGroup> balancerAddresses;
281302

282-
ResolutionResults(List<InetAddress> addresses, List<String> txtRecords) {
303+
ResolutionResults(
304+
List<InetAddress> addresses,
305+
List<String> txtRecords,
306+
List<EquivalentAddressGroup> balancerAddresses) {
283307
this.addresses = Collections.unmodifiableList(checkNotNull(addresses, "addresses"));
284308
this.txtRecords = Collections.unmodifiableList(checkNotNull(txtRecords, "txtRecords"));
309+
this.balancerAddresses =
310+
Collections.unmodifiableList(checkNotNull(balancerAddresses, "balancerAddresses"));
285311
}
286312
}
287313

@@ -305,14 +331,16 @@ ResolutionResults resolve(String host) throws Exception {
305331
ResolutionResults jdkResults = jdkResovler.resolve(host);
306332
List<InetAddress> addresses = jdkResults.addresses;
307333
List<String> txtRecords = Collections.emptyList();
334+
List<EquivalentAddressGroup> balancerAddresses = Collections.emptyList();
308335
try {
309336
ResolutionResults jdniResults = jndiResovler.resolve(host);
310337
txtRecords = jdniResults.txtRecords;
338+
balancerAddresses = jdniResults.balancerAddresses;
311339
} catch (Exception e) {
312340
logger.log(Level.SEVERE, "Failed to resolve TXT results", e);
313341
}
314342

315-
return new ResolutionResults(addresses, txtRecords);
343+
return new ResolutionResults(addresses, txtRecords, balancerAddresses);
316344
}
317345
}
318346

@@ -328,7 +356,8 @@ static final class JdkResolver extends DelegateResolver {
328356
ResolutionResults resolve(String host) throws Exception {
329357
return new ResolutionResults(
330358
Arrays.asList(InetAddress.getAllByName(host)),
331-
Collections.<String>emptyList());
359+
Collections.<String>emptyList(),
360+
Collections.<EquivalentAddressGroup>emptyList());
332361
}
333362
}
334363

@@ -340,26 +369,84 @@ ResolutionResults resolve(String host) throws Exception {
340369
@VisibleForTesting
341370
static final class JndiResolver extends DelegateResolver {
342371

343-
private static final String[] rrTypes = new String[]{"TXT"};
372+
private static final Pattern whitespace = Pattern.compile("\\s+");
344373

345374
@Override
346375
ResolutionResults resolve(String host) throws NamingException {
376+
List<String> serviceConfigTxtRecords = Collections.emptyList();
377+
String serviceConfigHostname = SERVICE_CONFIG_NAME_PREFIX + host;
378+
if (logger.isLoggable(Level.FINER)) {
379+
logger.log(
380+
Level.FINER, "About to query TXT records for {0}", new Object[]{serviceConfigHostname});
381+
}
382+
try {
383+
serviceConfigTxtRecords = getAllRecords("TXT", "dns:///" + serviceConfigHostname);
384+
} catch (NamingException e) {
385+
386+
if (logger.isLoggable(Level.FINE)) {
387+
logger.log(Level.FINE, "Unable to look up " + serviceConfigHostname, e);
388+
}
389+
}
390+
391+
String grpclbHostname = GRPCLB_NAME_PREFIX + host;
392+
if (logger.isLoggable(Level.FINER)) {
393+
logger.log(
394+
Level.FINER, "About to query SRV records for {0}", new Object[]{grpclbHostname});
395+
}
396+
List<EquivalentAddressGroup> balancerAddresses = Collections.emptyList();
397+
try {
398+
List<String> grpclbSrvRecords = getAllRecords("SRV", "dns:///" + grpclbHostname);
399+
balancerAddresses = new ArrayList<EquivalentAddressGroup>(grpclbSrvRecords.size());
400+
for (String srvRecord : grpclbSrvRecords) {
401+
try {
402+
String[] parts = whitespace.split(srvRecord);
403+
Verify.verify(parts.length == 4, "Bad SRV Record: %s, ", srvRecord);
404+
String srvHostname = parts[3];
405+
int port = Integer.parseInt(parts[2]);
406+
407+
InetAddress[] addrs = InetAddress.getAllByName(srvHostname);
408+
List<SocketAddress> sockaddrs = new ArrayList<SocketAddress>(addrs.length);
409+
for (InetAddress addr : addrs) {
410+
sockaddrs.add(new InetSocketAddress(addr, port));
411+
}
412+
Attributes attrs = Attributes.newBuilder()
413+
.set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, srvHostname)
414+
.build();
415+
balancerAddresses.add(
416+
new EquivalentAddressGroup(Collections.unmodifiableList(sockaddrs), attrs));
417+
} catch (UnknownHostException e) {
418+
logger.log(Level.WARNING, "Can't find address for SRV record" + srvRecord, e);
419+
} catch (RuntimeException e) {
420+
logger.log(Level.WARNING, "Failed to construct SRV record" + srvRecord, e);
421+
}
422+
}
423+
} catch (NamingException e) {
424+
if (logger.isLoggable(Level.FINE)) {
425+
logger.log(Level.FINE, "Unable to look up " + serviceConfigHostname, e);
426+
}
427+
}
347428

429+
return new ResolutionResults(
430+
/*addresses=*/ Collections.<InetAddress>emptyList(),
431+
serviceConfigTxtRecords,
432+
Collections.unmodifiableList(balancerAddresses));
433+
}
434+
435+
private List<String> getAllRecords(String recordType, String name) throws NamingException {
348436
InitialDirContext dirContext = new InitialDirContext();
349-
javax.naming.directory.Attributes attrs = dirContext.getAttributes("dns:///" + host, rrTypes);
350-
List<InetAddress> addresses = new ArrayList<InetAddress>();
351-
List<String> txtRecords = new ArrayList<String>();
437+
String[] rrType = new String[]{recordType};
438+
javax.naming.directory.Attributes attrs = dirContext.getAttributes(name, rrType);
439+
List<String> records = new ArrayList<String>();
352440

353441
NamingEnumeration<? extends Attribute> rrGroups = attrs.getAll();
354442
try {
355443
while (rrGroups.hasMore()) {
356444
Attribute rrEntry = rrGroups.next();
357-
assert Arrays.asList(rrTypes).contains(rrEntry.getID());
445+
assert Arrays.asList(rrType).contains(rrEntry.getID());
358446
NamingEnumeration<?> rrValues = rrEntry.getAll();
359447
try {
360448
while (rrValues.hasMore()) {
361-
String rrValue = (String) rrValues.next();
362-
txtRecords.add(rrValue);
449+
records.add(String.valueOf(rrValues.next()));
363450
}
364451
} finally {
365452
rrValues.close();
@@ -368,8 +455,7 @@ ResolutionResults resolve(String host) throws NamingException {
368455
} finally {
369456
rrGroups.close();
370457
}
371-
372-
return new ResolutionResults(addresses, txtRecords);
458+
return records;
373459
}
374460
}
375461
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2017, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import io.grpc.Attributes;
20+
import java.util.List;
21+
22+
/**
23+
* Special attributes that are only useful to gRPC.
24+
*/
25+
public final class GrpcAttributes {
26+
/**
27+
* Attribute key TXT DNS records.
28+
*/
29+
public static final Attributes.Key<List<String>> NAME_RESOLVER_ATTR_DNS_TXT =
30+
Attributes.Key.of("dns-txt");
31+
32+
/**
33+
* The naming authority of a gRPC LB server address. It is an address-group-level attribute,
34+
* present when the address group is a LoadBalancer.
35+
*/
36+
public static final Attributes.Key<String> ATTR_LB_ADDR_AUTHORITY =
37+
Attributes.Key.of("io.grpc.grpclb.lbAddrAuthority");
38+
39+
40+
private GrpcAttributes() {}
41+
}

core/src/main/java/io/grpc/internal/ManagedChannelImpl.java

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -906,27 +906,32 @@ public void onAddresses(final List<EquivalentAddressGroup> servers, final Attrib
906906
onError(Status.UNAVAILABLE.withDescription("NameResolver returned an empty list"));
907907
return;
908908
}
909-
logger.log(Level.FINE, "[{0}] resolved address: {1}, config={2}",
910-
new Object[] {getLogId(), servers, config});
911-
helper.runSerialized(new Runnable() {
912-
@Override
913-
public void run() {
914-
// Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
915-
if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
916-
return;
917-
}
918-
try {
919-
balancer.handleResolvedAddressGroups(servers, config);
920-
} catch (Throwable e) {
921-
logger.log(
922-
Level.WARNING, "[" + getLogId() + "] Unexpected exception from LoadBalancer", e);
923-
// It must be a bug! Push the exception back to LoadBalancer in the hope that it may
924-
// be propagated to the application.
925-
balancer.handleNameResolutionError(Status.INTERNAL.withCause(e)
926-
.withDescription("Thrown from handleResolvedAddresses(): " + e));
927-
}
909+
if (logger.isLoggable(Level.FINE)) {
910+
logger.log(Level.FINE, "[{0}] resolved address: {1}, config={2}",
911+
new Object[]{getLogId(), servers, config});
912+
}
913+
914+
final class NamesResolved implements Runnable {
915+
@Override
916+
public void run() {
917+
// Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
918+
if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
919+
return;
928920
}
929-
});
921+
try {
922+
balancer.handleResolvedAddressGroups(servers, config);
923+
} catch (Throwable e) {
924+
logger.log(
925+
Level.WARNING, "[" + getLogId() + "] Unexpected exception from LoadBalancer", e);
926+
// It must be a bug! Push the exception back to LoadBalancer in the hope that it may
927+
// be propagated to the application.
928+
balancer.handleNameResolutionError(Status.INTERNAL.withCause(e)
929+
.withDescription("Thrown from handleResolvedAddresses(): " + e));
930+
}
931+
}
932+
}
933+
934+
helper.runSerialized(new NamesResolved());
930935
}
931936

932937
@Override

core/src/test/java/io/grpc/internal/DnsNameResolverTest.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,25 @@ public void compositeResolverPrefersJdkAddressJndiTxt() throws Exception {
323323
DelegateResolver resolver = new DnsNameResolver.CompositeResolver(jdkDelegate, jndiDelegate);
324324

325325
List<InetAddress> jdkAnswer = createAddressList(2);
326-
jdkDelegate.addAnswer(jdkAnswer, Arrays.asList("jdktxt"));
326+
jdkDelegate.addAnswer(
327+
jdkAnswer,
328+
Arrays.asList("jdktxt"),
329+
Collections.<EquivalentAddressGroup>emptyList());
327330

328331
List<InetAddress> jdniAnswer = createAddressList(2);
329-
jndiDelegate.addAnswer(jdniAnswer, Arrays.asList("jnditxt"));
332+
jndiDelegate.addAnswer(
333+
jdniAnswer,
334+
Arrays.asList("jnditxt"),
335+
Collections.singletonList(
336+
new EquivalentAddressGroup(
337+
Collections.<SocketAddress>singletonList(new SocketAddress() {}),
338+
Attributes.EMPTY)));
330339

331340
ResolutionResults results = resolver.resolve("abc");
332341

333342
assertThat(results.addresses).containsExactlyElementsIn(jdkAnswer).inOrder();
334343
assertThat(results.txtRecords).containsExactly("jnditxt");
344+
assertThat(results.balancerAddresses).hasSize(1);
335345
}
336346

337347
@Test
@@ -418,14 +428,19 @@ private static class MockResolver extends DnsNameResolver.DelegateResolver {
418428
private final Queue<String> invocations = new LinkedList<String>();
419429

420430
MockResolver addAnswer(List<InetAddress> addresses) {
421-
return addAnswer(addresses, null);
431+
return addAnswer(addresses, null, null);
422432
}
423433

424-
MockResolver addAnswer(List<InetAddress> addresses, List<String> txtRecords) {
434+
MockResolver addAnswer(
435+
List<InetAddress> addresses,
436+
List<String> txtRecords,
437+
List<EquivalentAddressGroup> balancerAddresses) {
425438
answers.add(
426439
new ResolutionResults(
427440
addresses,
428-
MoreObjects.firstNonNull(txtRecords, Collections.<String>emptyList())));
441+
MoreObjects.firstNonNull(txtRecords, Collections.<String>emptyList()),
442+
MoreObjects.firstNonNull(
443+
balancerAddresses, Collections.<EquivalentAddressGroup>emptyList())));
429444
return this;
430445
}
431446

grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@ public enum LbPolicy {
4040
public static final Attributes.Key<LbPolicy> ATTR_LB_POLICY =
4141
Attributes.Key.of("io.grpc.grpclb.lbPolicy");
4242

43-
/**
44-
* The naming authority of an LB server address. It is an address-group-level attribute, present
45-
* when the address group is a LoadBalancer.
46-
*/
47-
public static final Attributes.Key<String> ATTR_LB_ADDR_AUTHORITY =
48-
Attributes.Key.of("io.grpc.grpclb.lbAddrAuthority");
49-
5043
/**
5144
* The opaque token given by the remote balancer for each returned server address. The client
5245
* will send this token with any requests sent to the associated server.

grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.grpc.LoadBalancer;
2828
import io.grpc.Status;
2929
import io.grpc.grpclb.GrpclbConstants.LbPolicy;
30+
import io.grpc.internal.GrpcAttributes;
3031
import io.grpc.internal.ObjectPool;
3132
import java.util.ArrayList;
3233
import java.util.Collections;
@@ -104,7 +105,7 @@ public void handleResolvedAddressGroups(
104105
List<LbAddressGroup> newLbAddressGroups = new ArrayList<LbAddressGroup>();
105106
List<EquivalentAddressGroup> newBackendServers = new ArrayList<EquivalentAddressGroup>();
106107
for (EquivalentAddressGroup server : updatedServers) {
107-
String lbAddrAuthority = server.getAttributes().get(GrpclbConstants.ATTR_LB_ADDR_AUTHORITY);
108+
String lbAddrAuthority = server.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY);
108109
if (lbAddrAuthority != null) {
109110
newLbAddressGroups.add(new LbAddressGroup(server, lbAddrAuthority));
110111
} else {

0 commit comments

Comments
 (0)