mirror of https://github.com/grpc/grpc-java.git
xds: Implement an xDS NameResolver/Provider for xDS load balancing alpha release (#6052)
* Implemented an XdsNameResolver that always returns a hard-coded service config * Implemented XdsNameResolverProvider * Added unit tests for XdsNameResolver and XdsNameResolverProvider * Added META-INF file for XdsNameReresolverProvider * Removed balancer name field in hard-coded service config * Changed URI scheme to xds-experimental. * Deleted unnecessary executors for running name resolution in a separate thread. * Fixed nits. * Fixed usage of GrpcUtil.getDefaultProxyDetector() as it was deleted. * Removed unnecessary shutdown implementation. * Replaced return with AssertionError as it hard-coded service config should never have error. * Removed unused name resolver args. * Added tail blank line.
This commit is contained in:
parent
9fa2608430
commit
1d04601313
|
|
@ -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.
|
||||
*
|
||||
* <p>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<String, ?> config;
|
||||
try {
|
||||
config = (Map<String, ?>) JsonParser.parse(SERVICE_CONFIG_HARDCODED);
|
||||
} catch (IOException e) {
|
||||
listener.onError(
|
||||
Status.UNKNOWN.withDescription("Invalid service config").withCause(e));
|
||||
throw new AssertionError("Invalid service config");
|
||||
}
|
||||
Attributes attrs =
|
||||
Attributes.newBuilder()
|
||||
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, config)
|
||||
.build();
|
||||
ResolutionResult result =
|
||||
ResolutionResult.newBuilder()
|
||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
||||
.setAttributes(attrs)
|
||||
.build();
|
||||
listener.onResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 com.google.common.base.Preconditions;
|
||||
import io.grpc.NameResolver.Args;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A provider for {@link XdsNameResolver}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
io.grpc.xds.XdsNameResolverProvider
|
||||
|
|
@ -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<ResolutionResult> resultCaptor;
|
||||
|
||||
@Test
|
||||
public void validName_withAuthority() {
|
||||
XdsNameResolver resolver =
|
||||
provider.newNameResolver(
|
||||
URI.create("xds-experimental://trafficdirector.google.com/foo.googleapis.com"), args);
|
||||
assertThat(resolver).isNotNull();
|
||||
assertThat(resolver.getServiceAuthority()).isEqualTo("foo.googleapis.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validName_noAuthority() {
|
||||
XdsNameResolver resolver =
|
||||
provider.newNameResolver(URI.create("xds-experimental:///foo.googleapis.com"), args);
|
||||
assertThat(resolver).isNotNull();
|
||||
assertThat(resolver.getServiceAuthority()).isEqualTo("foo.googleapis.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidName_hostnameContainsUnderscore() {
|
||||
try {
|
||||
provider.newNameResolver(URI.create("xds-experimental:///foo_bar.googleapis.com"), args);
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolve_hardcodedResult() {
|
||||
XdsNameResolver resolver = newResolver("foo.googleapis.com");
|
||||
resolver.start(mockListener);
|
||||
verify(mockListener).onResult(resultCaptor.capture());
|
||||
assertHardCodedServiceConfig(resultCaptor.getValue());
|
||||
|
||||
resolver = newResolver("bar.googleapis.com");
|
||||
resolver.start(mockListener);
|
||||
verify(mockListener, times(2)).onResult(resultCaptor.capture());
|
||||
assertHardCodedServiceConfig(resultCaptor.getValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void assertHardCodedServiceConfig(ResolutionResult actualResult) {
|
||||
assertThat(actualResult.getAddresses()).isEmpty();
|
||||
Map<String, ?> serviceConfig =
|
||||
actualResult.getAttributes().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
|
||||
List<Map<String, ?>> rawLbConfigs =
|
||||
(List<Map<String, ?>>) serviceConfig.get("loadBalancingConfig");
|
||||
Map<String, ?> xdsLbConfig = Iterables.getOnlyElement(rawLbConfigs);
|
||||
assertThat(xdsLbConfig.keySet()).containsExactly("xds_experimental");
|
||||
Map<String, ?> rawConfigValues = (Map<String, ?>) xdsLbConfig.get("xds_experimental");
|
||||
assertThat(rawConfigValues)
|
||||
.containsExactly("childPolicy",
|
||||
Collections.singletonList(
|
||||
Collections.singletonMap("round_robin", Collections.EMPTY_MAP)));
|
||||
}
|
||||
|
||||
private XdsNameResolver newResolver(String name) {
|
||||
return new XdsNameResolver(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 io.grpc.InternalServiceProviders;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import java.net.URI;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link XdsNameResolverProvider}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class XdsNameResolverProviderTest {
|
||||
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 XdsNameResolverProvider provider = new XdsNameResolverProvider();
|
||||
|
||||
@Test
|
||||
public void provided() {
|
||||
for (NameResolverProvider current
|
||||
: InternalServiceProviders.getCandidatesViaServiceLoader(
|
||||
NameResolverProvider.class, getClass().getClassLoader())) {
|
||||
if (current instanceof XdsNameResolverProvider) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("XdsNameResolverProvider not registered");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable() {
|
||||
assertThat(provider.isAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newNameResolver() {
|
||||
assertThat(
|
||||
provider.newNameResolver(URI.create("xds-experimental://1.1.1.1/foo.googleapis.com"), args))
|
||||
.isInstanceOf(XdsNameResolver.class);
|
||||
assertThat(
|
||||
provider.newNameResolver(URI.create("xds-experimental:///foo.googleapis.com"), args))
|
||||
.isInstanceOf(XdsNameResolver.class);
|
||||
assertThat(
|
||||
provider.newNameResolver(URI.create("notxds-experimental://1.1.1.1/foo.googleapis.com"),
|
||||
args))
|
||||
.isNull();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue