-
Notifications
You must be signed in to change notification settings - Fork 65
feat: Full Endpoint Resolution from EndpointContext #2313
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
Changes from 2 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,29 @@ 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.getResolvedEndpoint(); | ||
String universeDomain = endpointContext.getResolvedUniverseDomain(); | ||
|
||
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 +221,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 +271,7 @@ public static ClientContext create(StubSettings settings) throws IOException { | |
.setClock(clock) | ||
.setDefaultCallContext(defaultCallContext) | ||
.setServiceName(settings.getServiceName()) | ||
.setUniverseDomain(universeDomain) | ||
.setEndpoint(settings.getEndpoint()) | ||
.setQuotaProjectId(settings.getQuotaProjectId()) | ||
.setStreamWatchdog(watchdog) | ||
|
@@ -332,6 +340,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,18 @@ | |||||||||
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 { | ||||||||||
// Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint | ||||||||||
private static final String ENDPOINT_TEMPLATE = "SERVICE_NAME.UNIVERSE_DOMAIN:443"; | ||||||||||
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 +53,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,28 +77,76 @@ public abstract class EndpointContext { | |||||||||
@Nullable | ||||||||||
public abstract MtlsProvider mtlsProvider(); | ||||||||||
|
||||||||||
public abstract boolean usingGDCH(); | ||||||||||
|
||||||||||
public abstract Builder toBuilder(); | ||||||||||
|
||||||||||
private String resolvedEndpoint; | ||||||||||
private String resolvedUniverseDomain; | ||||||||||
|
||||||||||
public static Builder newBuilder() { | ||||||||||
return new AutoValue_EndpointContext.Builder().setSwitchToMtlsEndpointAllowed(false); | ||||||||||
return new AutoValue_EndpointContext.Builder() | ||||||||||
.setSwitchToMtlsEndpointAllowed(false) | ||||||||||
.setUsingGDCH(false); | ||||||||||
} | ||||||||||
|
||||||||||
/** Determines the fully resolved endpoint and universe domain values */ | ||||||||||
@VisibleForTesting | ||||||||||
void determineEndpoint() throws IOException { | ||||||||||
MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); | ||||||||||
// TransportChannelProvider's endpoint will override the ClientSettings' endpoint | ||||||||||
String customEndpoint = | ||||||||||
transportChannelProviderEndpoint() == null | ||||||||||
? clientSettingsEndpoint() | ||||||||||
: transportChannelProviderEndpoint(); | ||||||||||
resolvedEndpoint = | ||||||||||
mtlsEndpointResolver( | ||||||||||
customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); | ||||||||||
|
||||||||||
// GDC-H has a separate flow | ||||||||||
if (usingGDCH()) { | ||||||||||
determineGDCHEndpoint(customEndpoint); | ||||||||||
return; | ||||||||||
} | ||||||||||
// Check for "" (empty string) | ||||||||||
if (universeDomain() != null && universeDomain().isEmpty()) { | ||||||||||
throw new IllegalArgumentException("The universe domain value cannot be empty."); | ||||||||||
} | ||||||||||
// Universe Domain defaults to the GDU if it's not provided by the user. | ||||||||||
resolvedUniverseDomain = universeDomain() != null ? universeDomain() : GOOGLE_DEFAULT_UNIVERSE; | ||||||||||
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. Can we default the universe domain to GDU instead of introducing a new field 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. Sounds good, will do. 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.
Do we still need 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. I planned to remove it and use GDU by default, but I think there is a small edge case. Found it when running the IT GDCH tests, specifically regarding: sdk-platform-java/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITGdch.java Lines 189 to 192 in 3d885ba
L190's ClientContext.create(...) creates an EndpointContext. Nothing is passed in via the universeDomain getter as it's a GDC-H flow, but the universe domain would be set to GDU by default. L192's createStub() will create the Stub and it takes the params from the ClientContext (which has the GDU as the universe domain) and it will fail the GDC-H logic block. I'm not sure the way that the IT GDC-H tests are set is makes sense (or if I'm missing something), specifically regarding explicitly creating the ClientSettings -> ClientContext -> StubSettings steps. But I think it shows that doing this is possible by the user. I learned towards keeping the resolvedUniverseDomain so that the user can differentiate between what the user configuration and what the resolved universe domain is (null, GDU, user configured). This would possibly be done in a future enhancement as a getter. 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.
Makes sense. Please see my new comments regarding |
||||||||||
|
||||||||||
if (Strings.isNullOrEmpty(customEndpoint)) { | ||||||||||
customEndpoint = buildEndpointTemplate(serviceName(), resolvedUniverseDomain); | ||||||||||
resolvedEndpoint = | ||||||||||
mtlsEndpointResolver( | ||||||||||
customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); | ||||||||||
// Check if mTLS is configured with non-GDU | ||||||||||
if (resolvedEndpoint.equals(mtlsEndpoint()) | ||||||||||
blakeli0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
&& !resolvedUniverseDomain.equals(GOOGLE_DEFAULT_UNIVERSE)) { | ||||||||||
throw new IllegalArgumentException( | ||||||||||
"mTLS is not supported in any universe other than googleapis.com"); | ||||||||||
} | ||||||||||
} else { | ||||||||||
resolvedEndpoint = customEndpoint; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// GDC-H has no concept of Universe Domain. Do not set the resolvedUniverseDomain value | ||||||||||
private void determineGDCHEndpoint(String customEndpoint) { | ||||||||||
blakeli0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
if (universeDomain() != null) { | ||||||||||
throw new IllegalArgumentException( | ||||||||||
"Universe domain configuration is incompatible with GDC-H"); | ||||||||||
} else if (customEndpoint == null) { | ||||||||||
resolvedEndpoint = buildEndpointTemplate(serviceName(), GOOGLE_DEFAULT_UNIVERSE); | ||||||||||
} else { | ||||||||||
resolvedEndpoint = customEndpoint; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// Construct the endpoint with the template | ||||||||||
private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) { | ||||||||||
return ENDPOINT_TEMPLATE | ||||||||||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
.replace("SERVICE_NAME", serviceName) | ||||||||||
.replace("UNIVERSE_DOMAIN", resolvedUniverseDomain); | ||||||||||
} | ||||||||||
|
||||||||||
// 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( | ||||||||||
|
@@ -123,6 +179,14 @@ public String getResolvedEndpoint() { | |||||||||
return resolvedEndpoint; | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* The resolved Universe Domain is the computed Universe Domain after accounting for the custom | ||||||||||
* Universe Domain | ||||||||||
*/ | ||||||||||
public String getResolvedUniverseDomain() { | ||||||||||
return resolvedUniverseDomain; | ||||||||||
} | ||||||||||
|
||||||||||
@AutoValue.Builder | ||||||||||
public abstract static class Builder { | ||||||||||
/** | ||||||||||
|
@@ -132,6 +196,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,6 +215,8 @@ public abstract static class Builder { | |||||||||
|
||||||||||
public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); | ||||||||||
|
||||||||||
public abstract Builder setUsingGDCH(boolean usingGDCH); | ||||||||||
|
||||||||||
abstract EndpointContext autoBuild(); | ||||||||||
|
||||||||||
public EndpointContext build() throws IOException { | ||||||||||
|
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.