Skip to content

Commit bdbaab1

Browse files
committed
feat: Full endpoint resolution
1 parent 8822f3b commit bdbaab1

File tree

9 files changed

+435
-41
lines changed

9 files changed

+435
-41
lines changed

gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ private void logDirectPathMisconfig() {
295295
Level.WARNING,
296296
"DirectPath is misconfigured. DirectPath is only available in a GCE environment.");
297297
}
298+
if (!canUseDirectPathWithUniverseDomain()) {
299+
LOG.log(
300+
Level.WARNING, "DirectPath will only work in the the googleapis.com Universe Domain");
301+
}
298302
}
299303
}
300304
}
@@ -325,6 +329,10 @@ static boolean isOnComputeEngine() {
325329
return false;
326330
}
327331

332+
private boolean canUseDirectPathWithUniverseDomain() {
333+
return endpoint.contains("googleapis.com");
334+
}
335+
328336
@VisibleForTesting
329337
ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSecurityException {
330338
if (mtlsProvider.useMtlsClientCertificate()) {
@@ -356,7 +364,10 @@ private ManagedChannel createSingleChannel() throws IOException {
356364

357365
// Check DirectPath traffic.
358366
boolean useDirectPathXds = false;
359-
if (isDirectPathEnabled() && isNonDefaultServiceAccountAllowed() && isOnComputeEngine()) {
367+
if (isDirectPathEnabled()
368+
&& isNonDefaultServiceAccountAllowed()
369+
&& isOnComputeEngine()
370+
&& canUseDirectPathWithUniverseDomain()) {
360371
CallCredentials callCreds = MoreCallCredentials.from(credentials);
361372
ChannelCredentials channelCreds =
362373
GoogleDefaultChannelCredentials.newBuilder().callCredentials(callCreds).build();

gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java

+19
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ public void testDirectPathXdsEnabled() throws IOException {
290290
InstantiatingGrpcChannelProvider.newBuilder()
291291
.setAttemptDirectPath(true)
292292
.setAttemptDirectPathXds()
293+
.setEndpoint("test.googleapis.com:443")
293294
.build();
294295

295296
assertThat(provider.isDirectPathXdsEnabled()).isTrue();
@@ -528,6 +529,7 @@ public void testLogDirectPathMisconfigWrongCredential() {
528529
InstantiatingGrpcChannelProvider.newBuilder()
529530
.setAttemptDirectPathXds()
530531
.setAttemptDirectPath(true)
532+
.setEndpoint("test.googleapis.com:443")
531533
.build();
532534
assertThat(logHandler.getAllMessages())
533535
.contains(
@@ -545,6 +547,7 @@ public void testLogDirectPathMisconfigNotOnGCE() {
545547
.setAttemptDirectPathXds()
546548
.setAttemptDirectPath(true)
547549
.setAllowNonDefaultServiceAccount(true)
550+
.setEndpoint("test.googleapis.com:443")
548551
.build();
549552
if (!InstantiatingGrpcChannelProvider.isOnComputeEngine()) {
550553
assertThat(logHandler.getAllMessages())
@@ -554,6 +557,22 @@ public void testLogDirectPathMisconfigNotOnGCE() {
554557
InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler);
555558
}
556559

560+
@Test
561+
public void testLogDirectPathMisconfigNotInGDU() {
562+
FakeLogHandler logHandler = new FakeLogHandler();
563+
InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
564+
InstantiatingGrpcChannelProvider provider =
565+
InstantiatingGrpcChannelProvider.newBuilder()
566+
.setAttemptDirectPathXds()
567+
.setAttemptDirectPath(true)
568+
.setAllowNonDefaultServiceAccount(true)
569+
.setEndpoint("test.random.endpoint.com:443")
570+
.build();
571+
assertThat(logHandler.getAllMessages())
572+
.contains("DirectPath will only work in the the googleapis.com Universe Domain");
573+
InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler);
574+
}
575+
557576
private static class FakeLogHandler extends Handler {
558577
List<LogRecord> records = new ArrayList<>();
559578

gax-java/gax/clirr-ignored-differences.xml

+10
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,14 @@
2525
<className>com/google/api/gax/rpc/TransportChannelProvider</className>
2626
<method>* getEndpoint()</method>
2727
</difference>
28+
<difference>
29+
<differenceType>7012</differenceType>
30+
<className>com/google/api/gax/rpc/TransportChannelProvider</className>
31+
<method>* needsResolvedEndpoint()</method>
32+
<!-- Add Universe Domain to ClientContext -->
33+
<difference>
34+
<differenceType>7013</differenceType>
35+
<className>com/google/api/gax/rpc/ClientContext*</className>
36+
<method>* *UniverseDomain*(*)</method>
37+
</difference>
2838
</differences>

gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java

+23-13
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ public abstract class ClientContext {
104104
@Nullable
105105
abstract String getServiceName();
106106

107+
@Nullable
108+
public abstract String getUniverseDomain();
109+
107110
@Nullable
108111
public abstract String getEndpoint();
109112

@@ -157,15 +160,29 @@ public static ClientContext create(StubSettings settings) throws IOException {
157160
final ScheduledExecutorService backgroundExecutor = backgroundExecutorProvider.getExecutor();
158161

159162
Credentials credentials = settings.getCredentialsProvider().getCredentials();
163+
boolean usingGDCH = credentials instanceof GdchCredentials;
164+
EndpointContext endpointContext =
165+
EndpointContext.newBuilder()
166+
.setServiceName(settings.getServiceName())
167+
.setUniverseDomain(settings.getUniverseDomain())
168+
.setClientSettingsEndpoint(settings.getEndpoint())
169+
.setTransportChannelProviderEndpoint(
170+
settings.getTransportChannelProvider().getEndpoint())
171+
.setMtlsEndpoint(settings.getMtlsEndpoint())
172+
.setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed())
173+
.setUsingGDCH(usingGDCH)
174+
.build();
175+
String endpoint = endpointContext.getResolvedEndpoint();
176+
String universeDomain = endpointContext.getResolvedUniverseDomain();
160177

161178
String settingsGdchApiAudience = settings.getGdchApiAudience();
162-
if (credentials instanceof GdchCredentials) {
179+
if (usingGDCH) {
163180
// We recompute the GdchCredentials with the audience
164181
String audienceString;
165182
if (!Strings.isNullOrEmpty(settingsGdchApiAudience)) {
166183
audienceString = settingsGdchApiAudience;
167-
} else if (!Strings.isNullOrEmpty(settings.getEndpoint())) {
168-
audienceString = settings.getEndpoint();
184+
} else if (!Strings.isNullOrEmpty(endpoint)) {
185+
audienceString = endpoint;
169186
} else {
170187
throw new IllegalArgumentException("Could not infer GDCH api audience from settings");
171188
}
@@ -204,16 +221,6 @@ public static ClientContext create(StubSettings settings) throws IOException {
204221
if (transportChannelProvider.needsCredentials() && credentials != null) {
205222
transportChannelProvider = transportChannelProvider.withCredentials(credentials);
206223
}
207-
EndpointContext endpointContext =
208-
EndpointContext.newBuilder()
209-
.setServiceName(settings.getServiceName())
210-
.setClientSettingsEndpoint(settings.getEndpoint())
211-
.setTransportChannelProviderEndpoint(
212-
settings.getTransportChannelProvider().getEndpoint())
213-
.setMtlsEndpoint(settings.getMtlsEndpoint())
214-
.setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed())
215-
.build();
216-
String endpoint = endpointContext.getResolvedEndpoint();
217224
if (transportChannelProvider.needsEndpoint()) {
218225
transportChannelProvider = transportChannelProvider.withEndpoint(endpoint);
219226
}
@@ -264,6 +271,7 @@ public static ClientContext create(StubSettings settings) throws IOException {
264271
.setClock(clock)
265272
.setDefaultCallContext(defaultCallContext)
266273
.setServiceName(settings.getServiceName())
274+
.setUniverseDomain(universeDomain)
267275
.setEndpoint(settings.getEndpoint())
268276
.setQuotaProjectId(settings.getQuotaProjectId())
269277
.setStreamWatchdog(watchdog)
@@ -332,6 +340,8 @@ public abstract static class Builder {
332340
// Package-Private scope for internal use only. Shared between StubSettings and ClientContext
333341
abstract Builder setServiceName(String serviceName);
334342

343+
public abstract Builder setUniverseDomain(String universeDomain);
344+
335345
public abstract Builder setEndpoint(String endpoint);
336346

337347
public abstract Builder setQuotaProjectId(String QuotaProjectId);

gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java

+91-7
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,30 @@
3333
import com.google.api.gax.rpc.mtls.MtlsProvider;
3434
import com.google.auto.value.AutoValue;
3535
import com.google.common.annotations.VisibleForTesting;
36+
import com.google.common.base.Strings;
3637
import java.io.IOException;
3738
import javax.annotation.Nullable;
3839

39-
/** Contains the fields required to resolve the endpoint */
40+
/** Contains the fields required to resolve the endpoint and Universe Domain */
4041
@InternalApi
4142
@AutoValue
4243
public abstract class EndpointContext {
44+
// Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint
45+
private static final String ENDPOINT_TEMPLATE = "SERVICE_NAME.UNIVERSE_DOMAIN:443";
46+
static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com";
47+
48+
/**
49+
* ServiceName is host URI for Google Cloud Services. It follows the format of
50+
* `{ServiceName}.googleapis.com`. For example, speech.googleapis.com would have a ServiceName of
51+
* speech and cloudasset.googleapis.com would have a ServiceName of cloudasset.
52+
*/
53+
// TODO: Remove @Nullable after first release 2024 (Builder will default to "").
54+
@Nullable
55+
public abstract String serviceName();
56+
57+
@Nullable
58+
public abstract String universeDomain();
59+
4360
/**
4461
* ServiceName is host URI for Google Cloud Services. It follows the format of
4562
* `{ServiceName}.googleapis.com`. For example, speech.googleapis.com would have a ServiceName of
@@ -69,28 +86,76 @@ public abstract class EndpointContext {
6986
@Nullable
7087
public abstract MtlsProvider mtlsProvider();
7188

89+
public abstract boolean usingGDCH();
90+
7291
public abstract Builder toBuilder();
7392

7493
private String resolvedEndpoint;
94+
private String resolvedUniverseDomain;
7595

7696
public static Builder newBuilder() {
77-
return new AutoValue_EndpointContext.Builder().setSwitchToMtlsEndpointAllowed(false);
97+
return new AutoValue_EndpointContext.Builder()
98+
.setSwitchToMtlsEndpointAllowed(false)
99+
.setUsingGDCH(false);
78100
}
79101

102+
/** Determines the fully resolved endpoint and universe domain values */
80103
@VisibleForTesting
81104
void determineEndpoint() throws IOException {
82105
MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider();
106+
// TransportChannelProvider's endpoint will override the ClientSettings' endpoint
83107
String customEndpoint =
84108
transportChannelProviderEndpoint() == null
85109
? clientSettingsEndpoint()
86110
: transportChannelProviderEndpoint();
87-
resolvedEndpoint =
88-
mtlsEndpointResolver(
89-
customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider);
111+
112+
// GDC-H has a separate flow
113+
if (usingGDCH()) {
114+
determineGDCHEndpoint(customEndpoint);
115+
return;
116+
}
117+
// Check for "" (empty string)
118+
if (universeDomain() != null && universeDomain().isEmpty()) {
119+
throw new IllegalArgumentException("The universe domain value cannot be empty.");
120+
}
121+
// Universe Domain defaults to the GDU if it's not provided by the user.
122+
resolvedUniverseDomain = universeDomain() != null ? universeDomain() : GOOGLE_DEFAULT_UNIVERSE;
123+
124+
if (Strings.isNullOrEmpty(customEndpoint)) {
125+
customEndpoint = buildEndpointTemplate(serviceName(), resolvedUniverseDomain);
126+
resolvedEndpoint =
127+
mtlsEndpointResolver(
128+
customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider);
129+
// Check if mTLS is configured with non-GDU
130+
if (resolvedEndpoint.equals(mtlsEndpoint())
131+
&& !resolvedUniverseDomain.equals(GOOGLE_DEFAULT_UNIVERSE)) {
132+
throw new IllegalArgumentException(
133+
"mTLS is not supported in any universe other than googleapis.com");
134+
}
135+
} else {
136+
resolvedEndpoint = customEndpoint;
137+
}
138+
}
139+
140+
// GDC-H has no concept of Universe Domain. Do not set the resolvedUniverseDomain value
141+
private void determineGDCHEndpoint(String customEndpoint) {
142+
if (universeDomain() != null) {
143+
throw new IllegalArgumentException(
144+
"Universe domain configuration is incompatible with GDC-H");
145+
} else if (customEndpoint == null) {
146+
resolvedEndpoint = buildEndpointTemplate(serviceName(), GOOGLE_DEFAULT_UNIVERSE);
147+
} else {
148+
resolvedEndpoint = customEndpoint;
149+
}
150+
}
151+
152+
// Construct the endpoint with the template
153+
private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) {
154+
return ENDPOINT_TEMPLATE
155+
.replace("SERVICE_NAME", serviceName)
156+
.replace("UNIVERSE_DOMAIN", resolvedUniverseDomain);
90157
}
91158

92-
// This takes in parameters because determineEndpoint()'s logic will be updated
93-
// to pass custom values in.
94159
// Follows https://google.aip.dev/auth/4114 for resolving the endpoint
95160
@VisibleForTesting
96161
String mtlsEndpointResolver(
@@ -123,6 +188,14 @@ public String getResolvedEndpoint() {
123188
return resolvedEndpoint;
124189
}
125190

191+
/**
192+
* The resolved Universe Domain is the computed Universe Domain after accounting for the custom
193+
* Universe Domain
194+
*/
195+
public String getResolvedUniverseDomain() {
196+
return resolvedUniverseDomain;
197+
}
198+
126199
@AutoValue.Builder
127200
public abstract static class Builder {
128201
/**
@@ -132,6 +205,15 @@ public abstract static class Builder {
132205
*/
133206
public abstract Builder setServiceName(String serviceName);
134207

208+
public abstract Builder setUniverseDomain(String universeDomain);
209+
210+
/**
211+
* ServiceName is host URI for Google Cloud Services. It follows the format of
212+
* `{ServiceName}.googleapis.com`. For example, speech.googleapis.com would have a ServiceName
213+
* of speech and cloudasset.googleapis.com would have a ServiceName of cloudasset.
214+
*/
215+
public abstract Builder setServiceName(String serviceName);
216+
135217
/**
136218
* ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes.
137219
*/
@@ -149,6 +231,8 @@ public abstract static class Builder {
149231

150232
public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider);
151233

234+
public abstract Builder setUsingGDCH(boolean usingGDCH);
235+
152236
abstract EndpointContext autoBuild();
153237

154238
public EndpointContext build() throws IOException {

0 commit comments

Comments
 (0)