/* * Copyright 2015 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.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Objects; import com.google.errorprone.annotations.InlineMe; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URI; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; /** * A pluggable component that resolves a target {@link URI} and return addresses to the caller. * *
A {@code NameResolver} uses the URI's scheme to determine whether it can resolve it, and uses * the components after the scheme for actual resolution. * *
The addresses and attributes of a target may be changed over time, thus the caller registers a * {@link Listener} to receive continuous updates. * *
A {@code NameResolver} does not need to automatically re-resolve on failure. Instead, the * {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking * {@link #refresh()}. * *
Implementations don't need to be thread-safe. All methods are guaranteed to * be called sequentially. Additionally, all methods that have side-effects, i.e., * {@link #start(Listener2)}, {@link #shutdown} and {@link #refresh} are called from the same * {@link SynchronizationContext} as returned by {@link Args#getSynchronizationContext}. Do * not block within the synchronization context; blocking I/O and time-consuming tasks * should be offloaded to a separate thread, generally {@link Args#getOffloadExecutor}. * * @since 1.0.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770") public abstract class NameResolver { /** * Returns the authority used to authenticate connections to servers. It must be * from a trusted source, because if the authority is tampered with, RPCs may be sent to the * attackers which may leak sensitive user data. * *
An implementation must generate it without blocking, typically in line, and * must keep it unchanged. {@code NameResolver}s created from the same factory * with the same argument must return the same authority. * * @since 1.0.0 */ public abstract String getServiceAuthority(); /** * Starts the resolution. The method is not supposed to throw any exceptions. That might cause the * Channel that the name resolver is serving to crash. Errors should be propagated * through {@link Listener#onError}. * *
An instance may not be started more than once, by any overload of this method, even after
* an intervening call to {@link #shutdown}.
*
* @param listener used to receive updates on the target
* @since 1.0.0
*/
public void start(final Listener listener) {
if (listener instanceof Listener2) {
start((Listener2) listener);
} else {
start(new Listener2() {
@Override
public void onError(Status error) {
listener.onError(error);
}
@Override
public void onResult(ResolutionResult resolutionResult) {
StatusOr An instance may not be started more than once, by any overload of this method, even after
* an intervening call to {@link #shutdown}.
*
* @param listener used to receive updates on the target
* @since 1.21.0
*/
public void start(Listener2 listener) {
start((Listener) listener);
}
/**
* Stops the resolution. Updates to the Listener will stop.
*
* @since 1.0.0
*/
public abstract void shutdown();
/**
* Re-resolve the name.
*
* Can only be called after {@link #start} has been called.
*
* This is only a hint. Implementation takes it as a signal but may not start resolution
* immediately. It should never throw.
*
* The default implementation is no-op.
*
* @since 1.0.0
*/
public void refresh() {}
/**
* Factory that creates {@link NameResolver} instances.
*
* @since 1.0.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
public abstract static class Factory {
/**
* Creates a {@link NameResolver} for the given target URI, or {@code null} if the given URI
* cannot be resolved by this factory. The decision should be solely based on the scheme of the
* URI.
*
* This method will eventually be deprecated and removed as part of a migration from {@code
* java.net.URI} to {@code io.grpc.Uri}. Implementations will override {@link
* #newNameResolver(Uri, Args)} instead.
*
* @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
* @param args other information that may be useful
*
* @since 1.21.0
*/
public abstract NameResolver newNameResolver(URI targetUri, final Args args);
/**
* Creates a {@link NameResolver} for the given target URI.
*
* Implementations return {@code null} if 'targetUri' cannot be resolved by this factory. The
* decision should be solely based on the target's scheme.
*
* All {@link NameResolver.Factory} implementations should override this method, as it will
* eventually replace {@link #newNameResolver(URI, Args)}. For backwards compatibility, this
* default implementation delegates to {@link #newNameResolver(URI, Args)} if 'targetUri' can be
* converted to a java.net.URI.
*
* NB: Conversion is not always possible, for example {@code scheme:#frag} is a valid {@link
* Uri} but not a valid {@link URI} because its path is empty. The default implementation throws
* IllegalArgumentException in these cases.
*
* @param targetUri the target URI to be resolved
* @param args other information that may be useful
* @throws IllegalArgumentException if targetUri does not have the expected form
* @since 1.79
*/
public NameResolver newNameResolver(Uri targetUri, final Args args) {
// Not every io.grpc.Uri can be converted but in the ordinary ManagedChannel creation flow,
// any IllegalArgumentException thrown here would happened anyway, just earlier. That's
// because parse/toString is transparent so java.net.URI#create here sees the original target
// string just like it did before the io.grpc.Uri migration.
//
// Throwing IAE shouldn't surprise non-framework callers either. After all, many existing
// Factory impls are picky about targetUri and throw IAE when it doesn't look how they expect.
return newNameResolver(URI.create(targetUri.toString()), args);
}
/**
* Returns the default scheme, which will be used to construct a URI when {@link
* ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
* URI.
*
* @since 1.0.0
*/
public abstract String getDefaultScheme();
}
/**
* Receives address updates.
*
* All methods are expected to return quickly.
*
* @since 1.0.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
@ThreadSafe
public interface Listener {
/**
* Handles updates on resolved addresses and attributes.
*
* Implementations will not modify the given {@code servers}.
*
* @param servers the resolved server addresses. An empty list will trigger {@link #onError}
* @param attributes extra information from naming system.
* @since 1.3.0
*/
void onAddresses(
List All methods are expected to return quickly.
*
* This is a replacement API of {@code Listener}. However, we think this new API may change
* again, so we aren't yet encouraging mass-migration to it. It is fine to use and works.
*
* @since 1.21.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
public abstract static class Listener2 implements Listener {
/**
* Handles updates on resolved addresses and attributes.
*
* @deprecated This will be removed in 1.22.0
*/
@Override
@Deprecated
@InlineMe(
replacement = "this.onResult(ResolutionResult.newBuilder().setAddressesOrError("
+ "StatusOr.fromValue(servers)).setAttributes(attributes).build())",
imports = {"io.grpc.NameResolver.ResolutionResult", "io.grpc.StatusOr"})
public final void onAddresses(
List Newer NameResolver implementations should prefer calling onResult2. This method exists to
* facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
*
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
* @since 1.21.0
*/
public abstract void onResult(ResolutionResult resolutionResult);
/**
* Handles a name resolving error from the resolver. The listener is responsible for eventually
* invoking {@link NameResolver#refresh()} to re-attempt resolution.
*
* New NameResolver implementations should prefer calling onResult2 which will have the
* address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
* to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
*
* @param error a non-OK status
* @since 1.21.0
*/
@Override
public abstract void onError(Status error);
/**
* Handles updates on resolved addresses and attributes. Must be called from the same
* {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
* from the channel.
*
* @param resolutionResult the resolved server addresses or error in address resolution,
* attributes, and Service Config or error
* @return status indicating whether the resolutionResult was accepted by the listener,
* typically the result from a load balancer.
* @since 1.66
*/
public Status onResult2(ResolutionResult resolutionResult) {
onResult(resolutionResult);
return Status.OK;
}
}
/**
* Annotation for name resolution result attributes. It follows the annotation semantics defined
* by {@link Attributes}.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4972")
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface ResolutionResultAttr {}
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11989")
@ResolutionResultAttr
public static final Attributes.Key Args applicable to all {@link NameResolver}s are defined here using ordinary setters and
* getters. This container can also hold externally-defined "custom" args that aren't so widely
* useful or that would be inappropriate dependencies for this low level API. See {@link
* Args#getArg} for more.
*
* Note this class overrides neither {@code equals()} nor {@code hashCode()}.
*
* @since 1.21.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
public static final class Args {
private final int defaultPort;
private final ProxyDetector proxyDetector;
private final SynchronizationContext syncContext;
private final ServiceConfigParser serviceConfigParser;
@Nullable private final ScheduledExecutorService scheduledExecutorService;
@Nullable private final ChannelLogger channelLogger;
@Nullable private final Executor executor;
@Nullable private final String overrideAuthority;
private final MetricRecorder metricRecorder;
@Nullable private final NameResolverRegistry nameResolverRegistry;
@Nullable private final IdentityHashMap TODO: Only meaningful for InetSocketAddress producers. Make this a custom arg?
public int getDefaultPort() {
return defaultPort;
}
/**
* If the NameResolver wants to support proxy, it should inquire this {@link ProxyDetector}.
* See documentation on {@link ProxyDetector} about how proxies work in gRPC.
*
* @since 1.21.0
*/
public ProxyDetector getProxyDetector() {
return proxyDetector;
}
/**
* Returns the {@link SynchronizationContext} where {@link #start(Listener2)}, {@link #shutdown}
* and {@link #refresh} are run from.
*
* @since 1.21.0
*/
public SynchronizationContext getSynchronizationContext() {
return syncContext;
}
/**
* Returns a {@link ScheduledExecutorService} for scheduling delayed tasks.
*
* This service is a shared resource and is only meant for quick tasks. DO NOT block or run
* time-consuming tasks.
*
* The returned service doesn't support {@link ScheduledExecutorService#shutdown shutdown()}
* and {@link ScheduledExecutorService#shutdownNow shutdownNow()}. They will throw if called.
*
* @since 1.26.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6454")
public ScheduledExecutorService getScheduledExecutorService() {
if (scheduledExecutorService == null) {
throw new IllegalStateException("ScheduledExecutorService not set in Builder");
}
return scheduledExecutorService;
}
/**
* Returns the {@link ServiceConfigParser}.
*
* @since 1.21.0
*/
public ServiceConfigParser getServiceConfigParser() {
return serviceConfigParser;
}
/**
* Returns the value of a custom arg named 'key', or {@code null} if it's not set.
*
* While ordinary {@link Args} should be universally useful and meaningful, custom arguments
* can apply just to resolvers of a certain URI scheme, just to resolvers producing a particular
* type of {@link java.net.SocketAddress}, or even an individual {@link NameResolver} subclass.
* Custom args are identified by an instance of {@link Args.Key} which should be a constant
* defined in a java package and class appropriate for the argument's scope.
*
* {@link Args} are normally reserved for information in *support* of name resolution, not
* the name to be resolved itself. However, there are rare cases where all or part of the target
* name can't be represented by any standard URI scheme or can't be encoded as a String at all.
* Custom args, in contrast, can hold arbitrary Java types, making them a useful work around in
* these cases.
*
* Custom args can also be used simply to avoid adding inappropriate deps to the low level
* io.grpc package.
*/
@SuppressWarnings("unchecked") // Cast is safe because all put()s go through the setArg() API.
@Nullable
public Uses reference equality so keys should be defined as global constants.
*
* @param > addressesOrError =
resolutionResult.getAddressesOrError();
if (addressesOrError.hasValue()) {
listener.onAddresses(addressesOrError.getValue(),
resolutionResult.getAttributes());
} else {
listener.onError(addressesOrError.getStatus());
}
}
});
}
}
/**
* Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
* Channel that the name resolver is serving to crash. Errors should be propagated
* through {@link Listener2#onError}.
*
*
> addressesOrError;
@ResolutionResultAttr
private final Attributes attributes;
@Nullable
private final ConfigOrError serviceConfig;
ResolutionResult(
StatusOr
> addressesOrError,
@ResolutionResultAttr Attributes attributes,
ConfigOrError serviceConfig) {
this.addressesOrError = addressesOrError;
this.attributes = checkNotNull(attributes, "attributes");
this.serviceConfig = serviceConfig;
}
/**
* Constructs a new builder of a name resolution result.
*
* @since 1.21.0
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* Converts these results back to a builder.
*
* @since 1.21.0
*/
public Builder toBuilder() {
return newBuilder()
.setAddressesOrError(addressesOrError)
.setAttributes(attributes)
.setServiceConfig(serviceConfig);
}
/**
* Gets the addresses resolved by name resolution.
*
* @since 1.21.0
* @deprecated Will be superseded by getAddressesOrError
*/
@Deprecated
public List
> getAddressesOrError() {
return addressesOrError;
}
/**
* Gets the attributes associated with the addresses resolved by name resolution. If there are
* no attributes, {@link Attributes#EMPTY} will be returned.
*
* @since 1.21.0
*/
@ResolutionResultAttr
public Attributes getAttributes() {
return attributes;
}
/**
* Gets the Service Config parsed by {@link Args#getServiceConfigParser}.
*
* @since 1.21.0
*/
@Nullable
public ConfigOrError getServiceConfig() {
return serviceConfig;
}
@Override
public String toString() {
ToStringHelper stringHelper = MoreObjects.toStringHelper(this);
stringHelper.add("addressesOrError", addressesOrError.toString());
stringHelper.add("attributes", attributes);
stringHelper.add("serviceConfigOrError", serviceConfig);
return stringHelper.toString();
}
/**
* Useful for testing. May be slow to calculate.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ResolutionResult)) {
return false;
}
ResolutionResult that = (ResolutionResult) obj;
return Objects.equal(this.addressesOrError, that.addressesOrError)
&& Objects.equal(this.attributes, that.attributes)
&& Objects.equal(this.serviceConfig, that.serviceConfig);
}
/**
* Useful for testing. May be slow to calculate.
*/
@Override
public int hashCode() {
return Objects.hashCode(addressesOrError, attributes, serviceConfig);
}
/**
* A builder for {@link ResolutionResult}.
*
* @since 1.21.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
public static final class Builder {
private StatusOr
> addresses =
StatusOr.fromValue(Collections.emptyList());
private Attributes attributes = Attributes.EMPTY;
@Nullable
private ConfigOrError serviceConfig;
// Make sure to update #toBuilder above!
Builder() {}
/**
* Sets the addresses resolved by name resolution. This field is required.
*
* @since 1.21.0
* @deprecated Will be superseded by setAddressesOrError
*/
@Deprecated
public Builder setAddresses(List
> addresses) {
this.addresses = checkNotNull(addresses, "StatusOr addresses cannot be null.");
return this;
}
/**
* Sets the attributes for the addresses resolved by name resolution. If unset,
* {@link Attributes#EMPTY} will be used as a default.
*
* @since 1.21.0
*/
public Builder setAttributes(Attributes attributes) {
this.attributes = attributes;
return this;
}
/**
* Sets the Service Config parsed by {@link Args#getServiceConfigParser}.
* This field is optional.
*
* @since 1.21.0
*/
public Builder setServiceConfig(@Nullable ConfigOrError serviceConfig) {
this.serviceConfig = serviceConfig;
return this;
}
/**
* Constructs a new {@link ResolutionResult} from this builder.
*
* @since 1.21.0
*/
public ResolutionResult build() {
return new ResolutionResult(addresses, attributes, serviceConfig);
}
}
}
/**
* Represents either a successfully parsed service config, containing all necessary parts to be
* later applied by the channel, or a Status containing the error encountered while parsing.
*
* @since 1.20.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
public static final class ConfigOrError {
/**
* Returns a {@link ConfigOrError} for the successfully parsed config.
*/
public static ConfigOrError fromConfig(Object config) {
return new ConfigOrError(config);
}
/**
* Returns a {@link ConfigOrError} for the failure to parse the config.
*
* @param status a non-OK status
*/
public static ConfigOrError fromError(Status status) {
return new ConfigOrError(status);
}
private final Status status;
private final Object config;
private ConfigOrError(Object config) {
this.config = checkNotNull(config, "config");
this.status = null;
}
private ConfigOrError(Status status) {
this.config = null;
this.status = checkNotNull(status, "status");
checkArgument(!status.isOk(), "cannot use OK status: %s", status);
}
/**
* Returns config if exists, otherwise null.
*/
@Nullable
public Object getConfig() {
return config;
}
/**
* Returns error status if exists, otherwise null.
*/
@Nullable
public Status getError() {
return status;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConfigOrError that = (ConfigOrError) o;
return Objects.equal(status, that.status) && Objects.equal(config, that.config);
}
@Override
public int hashCode() {
return Objects.hashCode(status, config);
}
@Override
public String toString() {
if (config != null) {
return MoreObjects.toStringHelper(this)
.add("config", config)
.toString();
} else {
assert status != null;
return MoreObjects.toStringHelper(this)
.add("error", status)
.toString();
}
}
}
@Nullable
private static IdentityHashMap