New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Full Endpoint Resolution from EndpointContext #2313
Changes from 6 commits
bdbaab1
bf878b9
13aa12e
1857882
15d510e
653db8e
3d3d7a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,9 @@ public abstract class ClientContext { | |
@Nullable | ||
abstract String getServiceName(); | ||
|
||
@Nullable | ||
public abstract String getUniverseDomain(); | ||
|
||
@Nullable | ||
public abstract String getEndpoint(); | ||
|
||
|
@@ -157,15 +160,28 @@ public static ClientContext create(StubSettings settings) throws IOException { | |
final ScheduledExecutorService backgroundExecutor = backgroundExecutorProvider.getExecutor(); | ||
|
||
Credentials credentials = settings.getCredentialsProvider().getCredentials(); | ||
boolean usingGDCH = credentials instanceof GdchCredentials; | ||
EndpointContext endpointContext = | ||
EndpointContext.newBuilder() | ||
.setServiceName(settings.getServiceName()) | ||
.setUniverseDomain(settings.getUniverseDomain()) | ||
.setClientSettingsEndpoint(settings.getEndpoint()) | ||
.setTransportChannelProviderEndpoint( | ||
settings.getTransportChannelProvider().getEndpoint()) | ||
.setMtlsEndpoint(settings.getMtlsEndpoint()) | ||
.setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed()) | ||
.setUsingGDCH(usingGDCH) | ||
.build(); | ||
String endpoint = endpointContext.resolvedEndpoint(); | ||
|
||
String settingsGdchApiAudience = settings.getGdchApiAudience(); | ||
if (credentials instanceof GdchCredentials) { | ||
if (usingGDCH) { | ||
// We recompute the GdchCredentials with the audience | ||
String audienceString; | ||
if (!Strings.isNullOrEmpty(settingsGdchApiAudience)) { | ||
audienceString = settingsGdchApiAudience; | ||
} else if (!Strings.isNullOrEmpty(settings.getEndpoint())) { | ||
audienceString = settings.getEndpoint(); | ||
} else if (!Strings.isNullOrEmpty(endpoint)) { | ||
audienceString = endpoint; | ||
Comment on lines
+183
to
+184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirm this behavior is correct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The GDC-H audienceString's endpoint should match with endpoint set inside the TransportChannelProvider. The logic is that for a GDC-H flow, the resolved endpoint will be either the clientsettings endpoint or transportchannel endpoint if set. This matches the behavior below as either the clientSettings or transportchannel's endpoint. ClientSettings will only be set to the Transportchannel if it doesn't have an endpoint already set by the user. EndpointContext's resolvedEndpoint already takes this into consideration. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The else block is kept as the custom endpoint could be set as "" (empty string). |
||
} else { | ||
throw new IllegalArgumentException("Could not infer GDCH api audience from settings"); | ||
} | ||
|
@@ -204,16 +220,6 @@ public static ClientContext create(StubSettings settings) throws IOException { | |
if (transportChannelProvider.needsCredentials() && credentials != null) { | ||
transportChannelProvider = transportChannelProvider.withCredentials(credentials); | ||
} | ||
EndpointContext endpointContext = | ||
EndpointContext.newBuilder() | ||
.setServiceName(settings.getServiceName()) | ||
.setClientSettingsEndpoint(settings.getEndpoint()) | ||
.setTransportChannelProviderEndpoint( | ||
settings.getTransportChannelProvider().getEndpoint()) | ||
.setMtlsEndpoint(settings.getMtlsEndpoint()) | ||
.setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed()) | ||
.build(); | ||
String endpoint = endpointContext.getResolvedEndpoint(); | ||
if (transportChannelProvider.needsEndpoint()) { | ||
transportChannelProvider = transportChannelProvider.withEndpoint(endpoint); | ||
} | ||
|
@@ -264,6 +270,7 @@ public static ClientContext create(StubSettings settings) throws IOException { | |
.setClock(clock) | ||
.setDefaultCallContext(defaultCallContext) | ||
.setServiceName(settings.getServiceName()) | ||
.setUniverseDomain(settings.getUniverseDomain()) | ||
.setEndpoint(settings.getEndpoint()) | ||
.setQuotaProjectId(settings.getQuotaProjectId()) | ||
.setStreamWatchdog(watchdog) | ||
|
@@ -332,6 +339,8 @@ public abstract static class Builder { | |
// Package-Private scope for internal use only. Shared between StubSettings and ClientContext | ||
abstract Builder setServiceName(String serviceName); | ||
|
||
public abstract Builder setUniverseDomain(String universeDomain); | ||
|
||
public abstract Builder setEndpoint(String endpoint); | ||
|
||
public abstract Builder setQuotaProjectId(String QuotaProjectId); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,13 +33,16 @@ | |
import com.google.api.gax.rpc.mtls.MtlsProvider; | ||
import com.google.auto.value.AutoValue; | ||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.base.Strings; | ||
import java.io.IOException; | ||
import javax.annotation.Nullable; | ||
|
||
/** Contains the fields required to resolve the endpoint */ | ||
/** Contains the fields required to resolve the endpoint and Universe Domain */ | ||
@InternalApi | ||
@AutoValue | ||
public abstract class EndpointContext { | ||
static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; | ||
blakeli0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* ServiceName is host URI for Google Cloud Services. It follows the format of | ||
* `{ServiceName}.googleapis.com`. For example, speech.googleapis.com would have a ServiceName of | ||
|
@@ -48,6 +51,9 @@ public abstract class EndpointContext { | |
@Nullable | ||
public abstract String serviceName(); | ||
|
||
@Nullable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add some Javadocs to the getter here and setter below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Thanks |
||
public abstract String universeDomain(); | ||
|
||
/** | ||
* ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes. | ||
*/ | ||
|
@@ -69,58 +75,18 @@ public abstract class EndpointContext { | |
@Nullable | ||
public abstract MtlsProvider mtlsProvider(); | ||
|
||
public abstract Builder toBuilder(); | ||
|
||
private String resolvedEndpoint; | ||
public abstract boolean usingGDCH(); | ||
|
||
public static Builder newBuilder() { | ||
return new AutoValue_EndpointContext.Builder().setSwitchToMtlsEndpointAllowed(false); | ||
} | ||
abstract String resolvedUniverseDomain(); | ||
|
||
@VisibleForTesting | ||
void determineEndpoint() throws IOException { | ||
MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); | ||
String customEndpoint = | ||
transportChannelProviderEndpoint() == null | ||
? clientSettingsEndpoint() | ||
: transportChannelProviderEndpoint(); | ||
resolvedEndpoint = | ||
mtlsEndpointResolver( | ||
customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); | ||
} | ||
public abstract String resolvedEndpoint(); | ||
|
||
// This takes in parameters because determineEndpoint()'s logic will be updated | ||
// to pass custom values in. | ||
// Follows https://google.aip.dev/auth/4114 for resolving the endpoint | ||
@VisibleForTesting | ||
String mtlsEndpointResolver( | ||
String endpoint, | ||
String mtlsEndpoint, | ||
boolean switchToMtlsEndpointAllowed, | ||
MtlsProvider mtlsProvider) | ||
throws IOException { | ||
if (switchToMtlsEndpointAllowed && mtlsProvider != null) { | ||
switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { | ||
case ALWAYS: | ||
return mtlsEndpoint; | ||
case NEVER: | ||
return endpoint; | ||
default: | ||
if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { | ||
return mtlsEndpoint; | ||
} | ||
return endpoint; | ||
} | ||
} | ||
return endpoint; | ||
} | ||
public abstract Builder toBuilder(); | ||
|
||
/** | ||
* The resolved endpoint is the computed endpoint after accounting for the custom endpoints and | ||
* mTLS configurations. | ||
*/ | ||
public String getResolvedEndpoint() { | ||
return resolvedEndpoint; | ||
public static Builder newBuilder() { | ||
return new AutoValue_EndpointContext.Builder() | ||
.setSwitchToMtlsEndpointAllowed(false) | ||
.setUsingGDCH(false); | ||
} | ||
|
||
@AutoValue.Builder | ||
|
@@ -132,6 +98,8 @@ public abstract static class Builder { | |
*/ | ||
public abstract Builder setServiceName(String serviceName); | ||
|
||
public abstract Builder setUniverseDomain(String universeDomain); | ||
|
||
/** | ||
* ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes. | ||
*/ | ||
|
@@ -149,12 +117,119 @@ public abstract static class Builder { | |
|
||
public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); | ||
|
||
public abstract Builder setUsingGDCH(boolean usingGDCH); | ||
|
||
public abstract Builder setResolvedEndpoint(String resolvedEndpoint); | ||
|
||
public abstract Builder setResolvedUniverseDomain(String resolvedUniverseDomain); | ||
|
||
abstract String serviceName(); | ||
|
||
abstract String universeDomain(); | ||
|
||
abstract String clientSettingsEndpoint(); | ||
|
||
abstract String transportChannelProviderEndpoint(); | ||
|
||
abstract String mtlsEndpoint(); | ||
|
||
abstract boolean switchToMtlsEndpointAllowed(); | ||
|
||
abstract MtlsProvider mtlsProvider(); | ||
|
||
abstract boolean usingGDCH(); | ||
|
||
abstract String resolvedUniverseDomain(); | ||
|
||
abstract EndpointContext autoBuild(); | ||
|
||
private String determineUniverseDomain() { | ||
if (usingGDCH()) { | ||
// GDC-H has no concept of Universe Domain. User should not set a custom value | ||
if (universeDomain() != null) { | ||
throw new IllegalArgumentException( | ||
"Universe domain configuration is incompatible with GDC-H"); | ||
} | ||
return GOOGLE_DEFAULT_UNIVERSE; | ||
} | ||
// Check for "" (empty string) | ||
if (universeDomain() != null && universeDomain().isEmpty()) { | ||
throw new IllegalArgumentException("The universe domain value cannot be empty."); | ||
} | ||
// Override with user set universe domain if provided | ||
return universeDomain() != null ? universeDomain() : GOOGLE_DEFAULT_UNIVERSE; | ||
} | ||
|
||
/** Determines the fully resolved endpoint and universe domain values */ | ||
private String determineEndpoint() throws IOException { | ||
MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); | ||
// TransportChannelProvider's endpoint will override the ClientSettings' endpoint | ||
String customEndpoint = | ||
transportChannelProviderEndpoint() == null | ||
? clientSettingsEndpoint() | ||
: transportChannelProviderEndpoint(); | ||
|
||
// GDC-H has a separate flow | ||
if (usingGDCH()) { | ||
if (customEndpoint == null) { | ||
return buildEndpointTemplate(serviceName(), resolvedUniverseDomain()); | ||
} | ||
return customEndpoint; | ||
} | ||
|
||
// If user does not provide a custom endpoint, build one with the universe domain | ||
if (Strings.isNullOrEmpty(customEndpoint)) { | ||
customEndpoint = buildEndpointTemplate(serviceName(), resolvedUniverseDomain()); | ||
} | ||
|
||
String endpoint = | ||
mtlsEndpointResolver( | ||
customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); | ||
|
||
// Check if mTLS is configured with non-GDU | ||
if (endpoint.equals(mtlsEndpoint()) | ||
&& !resolvedUniverseDomain().equals(GOOGLE_DEFAULT_UNIVERSE)) { | ||
throw new IllegalArgumentException( | ||
"mTLS is not supported in any universe other than googleapis.com"); | ||
} | ||
|
||
return endpoint; | ||
} | ||
|
||
// Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint | ||
private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) { | ||
return serviceName + "." + resolvedUniverseDomain + ":443"; | ||
} | ||
|
||
// Follows https://google.aip.dev/auth/4114 for resolving the endpoint | ||
@VisibleForTesting | ||
String mtlsEndpointResolver( | ||
String endpoint, | ||
String mtlsEndpoint, | ||
boolean switchToMtlsEndpointAllowed, | ||
MtlsProvider mtlsProvider) | ||
throws IOException { | ||
if (switchToMtlsEndpointAllowed && mtlsProvider != null) { | ||
switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { | ||
case ALWAYS: | ||
return mtlsEndpoint; | ||
case NEVER: | ||
return endpoint; | ||
default: | ||
if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { | ||
return mtlsEndpoint; | ||
} | ||
return endpoint; | ||
} | ||
} | ||
return endpoint; | ||
} | ||
|
||
public EndpointContext build() throws IOException { | ||
EndpointContext endpointContext = autoBuild(); | ||
endpointContext.determineEndpoint(); | ||
return endpointContext; | ||
// The Universe Domain is used to resolve the Endpoint. It should be resolved first | ||
setResolvedUniverseDomain(determineUniverseDomain()); | ||
setResolvedEndpoint(determineEndpoint()); | ||
return autoBuild(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to expose a getter for universe domain? I guess it is for
StubSettings
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for the for logic that connects ClientContext <-> StubSettings. I believe it was added a while back for reasons and this getter is (ideally) only for StubSettings usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for every new
ClientSettings
, it seems that we have to duplicate it inStubSettings
as well.