diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java
index a0faf90300..9ca30f2c2d 100644
--- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java
+++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
import io.grpc.Attributes;
import io.grpc.EquivalentAddressGroup;
import io.grpc.NameResolver;
@@ -27,7 +28,9 @@ import io.grpc.Status;
import io.grpc.internal.SharedResourceHolder.Resource;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.net.URI;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -38,6 +41,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.naming.NamingEnumeration;
@@ -49,7 +53,7 @@ import javax.naming.directory.InitialDirContext;
* A DNS-based {@link NameResolver}.
*
*
Each {@code A} or {@code AAAA} record emits an {@link EquivalentAddressGroup} in the list
- * passed to {@link NameResolver.Listener#onUpdate}
+ * passed to {@link NameResolver.Listener#onAddresses(List, Attributes)}
*
* @see DnsNameResolverProvider
*/
@@ -59,8 +63,16 @@ final class DnsNameResolver extends NameResolver {
private static final boolean JNDI_AVAILABLE = jndiAvailable();
+ // From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
+ private static final String SERVICE_CONFIG_NAME_PREFIX = "_grpc_config.";
+ // From https://github.com/grpc/proposal/blob/master/A5-grpclb-in-dns.md
+ private static final String GRPCLB_NAME_PREFIX = "_grpclb._tcp.";
+
+ private static final String jndiProperty =
+ System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi", "false");
+
@VisibleForTesting
- static boolean enableJndi = false;
+ static boolean enableJndi = Boolean.parseBoolean(jndiProperty);
private DelegateResolver delegateResolver = pickDelegateResolver();
@@ -174,11 +186,19 @@ final class DnsNameResolver extends NameResolver {
return;
}
// Each address forms an EAG
- ArrayList servers = new ArrayList();
+ List servers = new ArrayList();
for (InetAddress inetAddr : resolvedInetAddrs.addresses) {
servers.add(new EquivalentAddressGroup(new InetSocketAddress(inetAddr, port)));
}
- savedListener.onAddresses(servers, Attributes.EMPTY);
+ servers.addAll(resolvedInetAddrs.balancerAddresses);
+
+ Attributes.Builder attrs = Attributes.newBuilder();
+ if (!resolvedInetAddrs.txtRecords.isEmpty()) {
+ attrs.set(
+ GrpcAttributes.NAME_RESOLVER_ATTR_DNS_TXT,
+ Collections.unmodifiableList(new ArrayList(resolvedInetAddrs.txtRecords)));
+ }
+ savedListener.onAddresses(servers, attrs.build());
} finally {
synchronized (DnsNameResolver.this) {
resolving = false;
@@ -278,10 +298,16 @@ final class DnsNameResolver extends NameResolver {
static final class ResolutionResults {
final List addresses;
final List txtRecords;
+ final List balancerAddresses;
- ResolutionResults(List addresses, List txtRecords) {
+ ResolutionResults(
+ List addresses,
+ List txtRecords,
+ List balancerAddresses) {
this.addresses = Collections.unmodifiableList(checkNotNull(addresses, "addresses"));
this.txtRecords = Collections.unmodifiableList(checkNotNull(txtRecords, "txtRecords"));
+ this.balancerAddresses =
+ Collections.unmodifiableList(checkNotNull(balancerAddresses, "balancerAddresses"));
}
}
@@ -305,14 +331,16 @@ final class DnsNameResolver extends NameResolver {
ResolutionResults jdkResults = jdkResovler.resolve(host);
List addresses = jdkResults.addresses;
List txtRecords = Collections.emptyList();
+ List balancerAddresses = Collections.emptyList();
try {
ResolutionResults jdniResults = jndiResovler.resolve(host);
txtRecords = jdniResults.txtRecords;
+ balancerAddresses = jdniResults.balancerAddresses;
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to resolve TXT results", e);
}
- return new ResolutionResults(addresses, txtRecords);
+ return new ResolutionResults(addresses, txtRecords, balancerAddresses);
}
}
@@ -328,7 +356,8 @@ final class DnsNameResolver extends NameResolver {
ResolutionResults resolve(String host) throws Exception {
return new ResolutionResults(
Arrays.asList(InetAddress.getAllByName(host)),
- Collections.emptyList());
+ Collections.emptyList(),
+ Collections.emptyList());
}
}
@@ -340,26 +369,84 @@ final class DnsNameResolver extends NameResolver {
@VisibleForTesting
static final class JndiResolver extends DelegateResolver {
- private static final String[] rrTypes = new String[]{"TXT"};
+ private static final Pattern whitespace = Pattern.compile("\\s+");
@Override
ResolutionResults resolve(String host) throws NamingException {
+ List serviceConfigTxtRecords = Collections.emptyList();
+ String serviceConfigHostname = SERVICE_CONFIG_NAME_PREFIX + host;
+ if (logger.isLoggable(Level.FINER)) {
+ logger.log(
+ Level.FINER, "About to query TXT records for {0}", new Object[]{serviceConfigHostname});
+ }
+ try {
+ serviceConfigTxtRecords = getAllRecords("TXT", "dns:///" + serviceConfigHostname);
+ } catch (NamingException e) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "Unable to look up " + serviceConfigHostname, e);
+ }
+ }
+
+ String grpclbHostname = GRPCLB_NAME_PREFIX + host;
+ if (logger.isLoggable(Level.FINER)) {
+ logger.log(
+ Level.FINER, "About to query SRV records for {0}", new Object[]{grpclbHostname});
+ }
+ List balancerAddresses = Collections.emptyList();
+ try {
+ List grpclbSrvRecords = getAllRecords("SRV", "dns:///" + grpclbHostname);
+ balancerAddresses = new ArrayList(grpclbSrvRecords.size());
+ for (String srvRecord : grpclbSrvRecords) {
+ try {
+ String[] parts = whitespace.split(srvRecord);
+ Verify.verify(parts.length == 4, "Bad SRV Record: %s, ", srvRecord);
+ String srvHostname = parts[3];
+ int port = Integer.parseInt(parts[2]);
+
+ InetAddress[] addrs = InetAddress.getAllByName(srvHostname);
+ List sockaddrs = new ArrayList(addrs.length);
+ for (InetAddress addr : addrs) {
+ sockaddrs.add(new InetSocketAddress(addr, port));
+ }
+ Attributes attrs = Attributes.newBuilder()
+ .set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, srvHostname)
+ .build();
+ balancerAddresses.add(
+ new EquivalentAddressGroup(Collections.unmodifiableList(sockaddrs), attrs));
+ } catch (UnknownHostException e) {
+ logger.log(Level.WARNING, "Can't find address for SRV record" + srvRecord, e);
+ } catch (RuntimeException e) {
+ logger.log(Level.WARNING, "Failed to construct SRV record" + srvRecord, e);
+ }
+ }
+ } catch (NamingException e) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "Unable to look up " + serviceConfigHostname, e);
+ }
+ }
+
+ return new ResolutionResults(
+ /*addresses=*/ Collections.emptyList(),
+ serviceConfigTxtRecords,
+ Collections.unmodifiableList(balancerAddresses));
+ }
+
+ private List getAllRecords(String recordType, String name) throws NamingException {
InitialDirContext dirContext = new InitialDirContext();
- javax.naming.directory.Attributes attrs = dirContext.getAttributes("dns:///" + host, rrTypes);
- List addresses = new ArrayList();
- List txtRecords = new ArrayList();
+ String[] rrType = new String[]{recordType};
+ javax.naming.directory.Attributes attrs = dirContext.getAttributes(name, rrType);
+ List records = new ArrayList();
NamingEnumeration extends Attribute> rrGroups = attrs.getAll();
try {
while (rrGroups.hasMore()) {
Attribute rrEntry = rrGroups.next();
- assert Arrays.asList(rrTypes).contains(rrEntry.getID());
+ assert Arrays.asList(rrType).contains(rrEntry.getID());
NamingEnumeration> rrValues = rrEntry.getAll();
try {
while (rrValues.hasMore()) {
- String rrValue = (String) rrValues.next();
- txtRecords.add(rrValue);
+ records.add(String.valueOf(rrValues.next()));
}
} finally {
rrValues.close();
@@ -368,8 +455,7 @@ final class DnsNameResolver extends NameResolver {
} finally {
rrGroups.close();
}
-
- return new ResolutionResults(addresses, txtRecords);
+ return records;
}
}
}
diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java
new file mode 100644
index 0000000000..b6d5e8a86a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import java.util.List;
+
+/**
+ * Special attributes that are only useful to gRPC.
+ */
+public final class GrpcAttributes {
+ /**
+ * Attribute key TXT DNS records.
+ */
+ public static final Attributes.Key> NAME_RESOLVER_ATTR_DNS_TXT =
+ Attributes.Key.of("dns-txt");
+
+ /**
+ * The naming authority of a gRPC LB server address. It is an address-group-level attribute,
+ * present when the address group is a LoadBalancer.
+ */
+ public static final Attributes.Key ATTR_LB_ADDR_AUTHORITY =
+ Attributes.Key.of("io.grpc.grpclb.lbAddrAuthority");
+
+
+ private GrpcAttributes() {}
+}
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
index 5c39dc759f..4458dd6c2b 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
@@ -906,27 +906,32 @@ public final class ManagedChannelImpl extends ManagedChannel {
onError(Status.UNAVAILABLE.withDescription("NameResolver returned an empty list"));
return;
}
- logger.log(Level.FINE, "[{0}] resolved address: {1}, config={2}",
- new Object[] {getLogId(), servers, config});
- helper.runSerialized(new Runnable() {
- @Override
- public void run() {
- // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
- if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
- return;
- }
- try {
- balancer.handleResolvedAddressGroups(servers, config);
- } catch (Throwable e) {
- logger.log(
- Level.WARNING, "[" + getLogId() + "] Unexpected exception from LoadBalancer", e);
- // It must be a bug! Push the exception back to LoadBalancer in the hope that it may
- // be propagated to the application.
- balancer.handleNameResolutionError(Status.INTERNAL.withCause(e)
- .withDescription("Thrown from handleResolvedAddresses(): " + e));
- }
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "[{0}] resolved address: {1}, config={2}",
+ new Object[]{getLogId(), servers, config});
+ }
+
+ final class NamesResolved implements Runnable {
+ @Override
+ public void run() {
+ // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match.
+ if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
+ return;
}
- });
+ try {
+ balancer.handleResolvedAddressGroups(servers, config);
+ } catch (Throwable e) {
+ logger.log(
+ Level.WARNING, "[" + getLogId() + "] Unexpected exception from LoadBalancer", e);
+ // It must be a bug! Push the exception back to LoadBalancer in the hope that it may
+ // be propagated to the application.
+ balancer.handleNameResolutionError(Status.INTERNAL.withCause(e)
+ .withDescription("Thrown from handleResolvedAddresses(): " + e));
+ }
+ }
+ }
+
+ helper.runSerialized(new NamesResolved());
}
@Override
diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
index ee0324f1de..7183217e3b 100644
--- a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
+++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
@@ -323,15 +323,25 @@ public class DnsNameResolverTest {
DelegateResolver resolver = new DnsNameResolver.CompositeResolver(jdkDelegate, jndiDelegate);
List jdkAnswer = createAddressList(2);
- jdkDelegate.addAnswer(jdkAnswer, Arrays.asList("jdktxt"));
+ jdkDelegate.addAnswer(
+ jdkAnswer,
+ Arrays.asList("jdktxt"),
+ Collections.emptyList());
List jdniAnswer = createAddressList(2);
- jndiDelegate.addAnswer(jdniAnswer, Arrays.asList("jnditxt"));
+ jndiDelegate.addAnswer(
+ jdniAnswer,
+ Arrays.asList("jnditxt"),
+ Collections.singletonList(
+ new EquivalentAddressGroup(
+ Collections.singletonList(new SocketAddress() {}),
+ Attributes.EMPTY)));
ResolutionResults results = resolver.resolve("abc");
assertThat(results.addresses).containsExactlyElementsIn(jdkAnswer).inOrder();
assertThat(results.txtRecords).containsExactly("jnditxt");
+ assertThat(results.balancerAddresses).hasSize(1);
}
@Test
@@ -418,14 +428,19 @@ public class DnsNameResolverTest {
private final Queue invocations = new LinkedList();
MockResolver addAnswer(List addresses) {
- return addAnswer(addresses, null);
+ return addAnswer(addresses, null, null);
}
- MockResolver addAnswer(List addresses, List txtRecords) {
+ MockResolver addAnswer(
+ List addresses,
+ List txtRecords,
+ List balancerAddresses) {
answers.add(
new ResolutionResults(
addresses,
- MoreObjects.firstNonNull(txtRecords, Collections.emptyList())));
+ MoreObjects.firstNonNull(txtRecords, Collections.emptyList()),
+ MoreObjects.firstNonNull(
+ balancerAddresses, Collections.emptyList())));
return this;
}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java
index 99745aac7a..4e72074660 100644
--- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java
@@ -40,13 +40,6 @@ public final class GrpclbConstants {
public static final Attributes.Key ATTR_LB_POLICY =
Attributes.Key.of("io.grpc.grpclb.lbPolicy");
- /**
- * The naming authority of an LB server address. It is an address-group-level attribute, present
- * when the address group is a LoadBalancer.
- */
- public static final Attributes.Key ATTR_LB_ADDR_AUTHORITY =
- Attributes.Key.of("io.grpc.grpclb.lbAddrAuthority");
-
/**
* The opaque token given by the remote balancer for each returned server address. The client
* will send this token with any requests sent to the associated server.
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java
index ec7326eecb..adc280bb5d 100644
--- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java
@@ -27,6 +27,7 @@ import io.grpc.InternalWithLogId;
import io.grpc.LoadBalancer;
import io.grpc.Status;
import io.grpc.grpclb.GrpclbConstants.LbPolicy;
+import io.grpc.internal.GrpcAttributes;
import io.grpc.internal.ObjectPool;
import java.util.ArrayList;
import java.util.Collections;
@@ -104,7 +105,7 @@ class GrpclbLoadBalancer extends LoadBalancer implements InternalWithLogId {
List newLbAddressGroups = new ArrayList();
List newBackendServers = new ArrayList();
for (EquivalentAddressGroup server : updatedServers) {
- String lbAddrAuthority = server.getAttributes().get(GrpclbConstants.ATTR_LB_ADDR_AUTHORITY);
+ String lbAddrAuthority = server.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY);
if (lbAddrAuthority != null) {
newLbAddressGroups.add(new LbAddressGroup(server, lbAddrAuthority));
} else {
diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
index 3feb985f93..41c081a1ce 100644
--- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
+++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
@@ -71,6 +71,7 @@ import io.grpc.grpclb.GrpclbState.RoundRobinPicker;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.internal.FakeClock;
+import io.grpc.internal.GrpcAttributes;
import io.grpc.internal.ObjectPool;
import io.grpc.internal.SerializingExecutor;
import io.grpc.stub.StreamObserver;
@@ -1623,7 +1624,7 @@ public class GrpclbLoadBalancerTest {
private static Attributes lbAttributes(String authority) {
return Attributes.newBuilder()
- .set(GrpclbConstants.ATTR_LB_ADDR_AUTHORITY, authority)
+ .set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, authority)
.build();
}