-
Notifications
You must be signed in to change notification settings - Fork 699
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: Add Universe Domain Support #2435
Conversation
this.httpRequestInitializer = httpRequestInitializer; | ||
this.serviceName = parseServiceName(rootUrl); | ||
this.isUserConfiguredEndpoint = |
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.
Why do we need to determine if the rootUrl
is user configured based on if it ends with GDU in the Builder
? Is setting this.isUserConfiguredEndpoint = true;
in the setter not enough ?
Is it for the scenarios that customers may extend this class and call this Builder directly? Because I think in the public client, rootUrl
is hidden. e.g. in Bigquery
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.
Is it for the scenarios that customers may extend this class and call this Builder directly?
Yep, it's for the (hopefully) rare user use-case where users are directly extending this class and calling the builder. The apiary libraries don't expose the endpoint configurations in the Builder directly.
* done via {@link #setRootUrl(String)}. | ||
* | ||
* <p>For other uses cases that touch this Builder's constructor directly, check if the rootUrl | ||
* passed in references the Google Default Universe (GDU). Any rootUrl value that is not set in |
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.
I don't think this logic is correct, customers could set regional endpoints which also end with GDU, see https://cloud.google.com/storage/docs/regional-endpoints
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.
Ah. Turns out I think this works, but for the wrong reasons. I believe we're parsing out the serviceName and then reconstructing it to build the the same endpoint later on. It's marked as a non-user configured endpoint when it should be.
I think it might be better if I change the isUserConfiguredEndpoint
logic to match for a strict regex which should cover the regional endpoints edge case.
Why they have to set both rootUrl and universeDomain? I think setting universeDomain alone should be good enough? |
The apiary wrapped libraries (java-storage and java-bigquery) may have some user configurations where they change the rootUrl value via settings there (for emulators, local endpoint, etc). We'll need those configurations so that we won't override them when the apiary library tries to resolve the endpoint in the code here. |
I see. If that's the case, I would say we don't have to pass
|
I believe you're right regarding not needing the universe domain to resolve the host. I just realized that the universe domain is also needed as we need it to validate the universe domain. If not passed to the Apiary client, the resolved universe domain will always default to Previously, the handwritten libraries were validating the universe domain, but since we're moving it the apiary client, I believe we'll need to pass the configured universe domain value here. |
It maybe a scenario we missed before, do we still want to validate the universe domain if the endpoint/rootUrl is provided? I think we should not because the endpoint configuration would override the universe domain configuration if both are configured. Otherwise it would be confusing if we pass both to the client, use one for validation but use another one for making calls. |
expectedUniverseDomain = credentials.getUniverseDomain(); | ||
} | ||
if (!expectedUniverseDomain.equals(getUniverseDomain())) { | ||
throw new 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.
Why is it an IOException
? It is a comparison that does not involve IO operations, hence I think it should be a runtime exception.
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.
Ah right. Will change.
* <p>The roolUrl from the Discovery Docs will always follow the format of | ||
* https://{serviceName}(.mtls).googleapis.com/ | ||
*/ | ||
private String parseServiceName(String rootUrl) { |
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.
I think we can use the regex to extract the service name now.
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.
Sounds good, will update,
It is a bit of weird scenario especially given Apiary's non-normal use case. I think we should be validating the universe domain even if we are providing the "correct" endpoint from ServiceOptions. Off the top of my head, I can think of these scenarios in which I think validation is needed required:
Agreed. Ideally the endpoint should be resolved inside the Apiary client itself for every use case. It's only a special case for these two handwritten apiary wrappers as Java-Core is used to resolved the endpoint. I chose this path because Java-Core sets the the host value to be the value of DEFUALT_HOST (https://github.com/googleapis/sdk-platform-java/blob/4b44a7851dc1d4fd2ac21a54df6c24db5625223c/java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java#L86) if nothing is configured. One option that I thought about would be to have the two libraries do some additional logic to filter this out.
I didn't go with this path since it's additional work for the two handwritten libraries. |
* discovery doc. Follows the format of `https://{serviceName}(.mtls).googleapis.com/` | ||
*/ | ||
Pattern defaultEndpointRegex = | ||
Pattern.compile("https://[a-zA-Z]*(\\.mtls)?\\.googleapis.com/?"); |
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.
I did a few quick test but I don't think this regex works, what worked for me is
https:\/\/[a-zA-Z]*(\.mtls)?\.googleapis.com\/?
Can you please make sure the regex is correct and add a few tests just for the regex?
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.
Oh huh, I did add unit tests for this regex and it did pass:
Lines 328 to 357 in 1cfd93a
@Test | |
public void testIsUserSetEndpoint_mtlsRootUrl() { | |
AbstractGoogleClient.Builder clientBuilder = | |
new MockGoogleClient.Builder( | |
TRANSPORT, "https://test.mtls.googleapis.com/", "", JSON_OBJECT_PARSER, null) | |
.setApplicationName("Test Application"); | |
assertFalse(clientBuilder.isUserConfiguredEndpoint); | |
} | |
@Test | |
public void testIsUserSetEndpoint_nonGDURootUrl() { | |
AbstractGoogleClient.Builder clientBuilder = | |
new MockGoogleClient.Builder( | |
TRANSPORT, "https://test.random.com/", "", JSON_OBJECT_PARSER, null) | |
.setApplicationName("Test Application"); | |
assertTrue(clientBuilder.isUserConfiguredEndpoint); | |
} | |
@Test | |
public void testIsUserSetEndpoint_regionalEndpoint() { | |
AbstractGoogleClient.Builder clientBuilder = | |
new MockGoogleClient.Builder( | |
TRANSPORT, | |
"https://us-east-4.coolservice.googleapis.com/", | |
"", | |
JSON_OBJECT_PARSER, | |
null) | |
.setApplicationName("Test Application"); | |
assertTrue(clientBuilder.isUserConfiguredEndpoint); | |
} |
this.httpRequestInitializer = httpRequestInitializer; | ||
this.serviceName = parseServiceName(rootUrl); | ||
this.isUserConfiguredEndpoint = !defaultEndpointRegex.matcher(this.rootUrl).matches(); |
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.
I'm still not convinced that we need this. Considering the following scenarios:
rootUrl
is set to non-GDU,serviceName
would be parsed to null, hence we would userootUrl
as is indeterminingEndpoint()
.rootUrl
is set to GDU,isUserConfiguredEndpoint
would be false anyway, and we would re-create therootUrl
withserviceName
anduniverseDomain
.
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 does work, but I think it has misleading logic inside for certain edge cases (i.e. regional endpoints). For example, when using a regional endpoint in speech: https://eu-speech.googleapis.com
and passing that into the constructor, the logic in this PR would work. But I believe it sets the serviceName as eu-speech
, when it should just be speech
, and builds the endpoint, when it probably shouldn't because it is a custom endpoint.
The more I think about this, the more I'm leaning towards fixing the serviceName parsing logic the same way we did in GAPICs. The Apiary generator probably should be the one parsing the discovery doc and generating this value as a constant inside the library. That way we don't have worry about parsing the serviceName based on user params or user set values and the Apiary would have this value by default. I probably should have done that to begin with, but I didn't account for regional endpoints and I thought this way would be fine.
* Domain in the Credentials | ||
*/ | ||
public void validateUniverseDomain() throws IOException { | ||
if (httpRequestInitializer instanceof HttpCredentialsAdapter) { |
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.
nit: Change this to
if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) {
return;
}
for better readability.
@@ -499,6 +513,7 @@ | |||
<project.httpclient.version>4.5.14</project.httpclient.version> | |||
<project.commons-codec.version>1.16.0</project.commons-codec.version> | |||
<project.oauth.version>1.35.0</project.oauth.version> | |||
<project.auth.version>1.22.0</project.auth.version> |
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.
We might discussed this in the past, but I'm a little surprised that this project is not dependent on auth library yet. We probably can not avoid it because we need to access the Credentials
object for validation, but let's be more careful here as it may introduce dependency problem for customers. @suztomo Do you have any concerns regarding this new auth dependency?
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.
Universe Domain support can be done almost all inside this PR. There is an additional change required inside the google-api-client-services generator. Need to add the setter inside the
{Service}.Builder
implementation.The reason this needs to be in the generated client code is because it returns the parent's implementation returns a type of
AbstractGoogleClient.Builder
instead ofBigquery.Builder
. Having this inside the child implementation would return the correct type (Bigquery.Builder
).Tested this locally with a local build of Bigquery Apiary client and a local build of the api-client library. Connection to the test environment works and the validation is called on each RPC invocation.
Changes required in Java-Storage and Java-Bigquery
Local Tests
Next Steps:
PR in google-api-java-client-services: googleapis/google-api-java-client-services#19934