diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java new file mode 100644 index 0000000000..f71478570f --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -0,0 +1,92 @@ +/* + * Copyright 2019 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.xds; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Preconditions; +import io.grpc.Attributes; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.Status; +import io.grpc.internal.GrpcAttributes; +import io.grpc.internal.JsonParser; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Map; + +/** + * A {@link NameResolver} for resolving gRPC target names with "xds-experimental" scheme. + * + *
The implementation is for load balancing alpha release only. No actual VHDS is involved. It
+ * always returns a hard-coded service config that selects the xds_experimental LB policy with
+ * round-robin as the child policy.
+ *
+ * @see XdsNameResolverProvider
+ */
+final class XdsNameResolver extends NameResolver {
+
+ private static final String SERVICE_CONFIG_HARDCODED = "{"
+ + "\"loadBalancingConfig\": ["
+ + "{\"xds_experimental\" : {"
+ + "\"childPolicy\" : [{\"round_robin\" : {}}]"
+ + "}}"
+ + "]}";
+
+ private final String authority;
+
+ XdsNameResolver(String name) {
+ URI nameUri = URI.create("//" + checkNotNull(name, "name"));
+ Preconditions.checkArgument(nameUri.getHost() != null, "Invalid hostname: %s", name);
+ authority =
+ Preconditions.checkNotNull(
+ nameUri.getAuthority(), "nameUri (%s) doesn't have an authority", nameUri);
+ }
+
+ @Override
+ public String getServiceAuthority() {
+ return authority;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void start(final Listener2 listener) {
+ Map It resolves a target URI whose scheme is {@code "xds-experimental"}. The authority of the
+ * target URI is never used for current release. The path of the target URI, excluding the leading
+ * slash {@code '/'}, will indicate the name to use in the VHDS query.
+ *
+ * This class should not be directly referenced in code. The resolver should be accessed
+ * through {@link io.grpc.NameResolverRegistry#asFactory#newNameResolver(URI, Args)} with the URI
+ * scheme "xds-experimental".
+ */
+public final class XdsNameResolverProvider extends NameResolverProvider {
+
+ private static final String SCHEME = "xds-experimental";
+
+ @Override
+ public XdsNameResolver newNameResolver(URI targetUri, Args args) {
+ if (SCHEME.equals(targetUri.getScheme())) {
+ String targetPath = Preconditions.checkNotNull(targetUri.getPath(), "targetPath");
+ Preconditions.checkArgument(
+ targetPath.startsWith("/"),
+ "the path component (%s) of the target (%s) must start with '/'",
+ targetPath,
+ targetUri);
+ String name = targetPath.substring(1);
+ return new XdsNameResolver(name);
+ }
+ return null;
+ }
+
+ @Override
+ public String getDefaultScheme() {
+ return SCHEME;
+ }
+
+ @Override
+ protected boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ protected int priority() {
+ // Set priority value to be < 5 as we still want DNS resolver to be the primary default
+ // resolver.
+ return 4;
+ }
+}
diff --git a/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider b/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
new file mode 100644
index 0000000000..269cdd3880
--- /dev/null
+++ b/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
@@ -0,0 +1 @@
+io.grpc.xds.XdsNameResolverProvider
diff --git a/xds/src/test/java/io/grpc/xds/XdsNamResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNamResolverTest.java
new file mode 100644
index 0000000000..cc2ea47981
--- /dev/null
+++ b/xds/src/test/java/io/grpc/xds/XdsNamResolverTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019 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.xds;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.Iterables;
+import io.grpc.NameResolver;
+import io.grpc.NameResolver.ResolutionResult;
+import io.grpc.NameResolver.ServiceConfigParser;
+import io.grpc.SynchronizationContext;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.internal.GrpcUtil;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Unit tests for {@link XdsNameResolver}. */
+@RunWith(JUnit4.class)
+public class XdsNamResolverTest {
+
+ @Rule public final MockitoRule mocks = MockitoJUnit.rule();
+
+ private final SynchronizationContext syncContext = new SynchronizationContext(
+ new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ throw new AssertionError(e);
+ }
+ });
+
+ private final NameResolver.Args args =
+ NameResolver.Args.newBuilder()
+ .setDefaultPort(8080)
+ .setProxyDetector(GrpcUtil.NOOP_PROXY_DETECTOR)
+ .setSynchronizationContext(syncContext)
+ .setServiceConfigParser(mock(ServiceConfigParser.class))
+ .build();
+
+ private final XdsNameResolverProvider provider = new XdsNameResolverProvider();
+
+ @Mock private NameResolver.Listener2 mockListener;
+ @Captor private ArgumentCaptor