Skip to content
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

fix: Allow custom HttpRules for REST LROs #1288

Merged
merged 92 commits into from
Mar 10, 2023
Merged

Conversation

lqiu96
Copy link
Contributor

@lqiu96 lqiu96 commented Jan 31, 2023

Fixes: #1279

This is both a gapic-generator and gax-java change:

  • Gapic Generator has a map of the Custom Http Bindings inside HttpJson{Service}Stub.java and passes it to the HttpJsonOperationStub. The generator parses the ServiceYaml file and recreates the HttpRules to be sent over.
  • Gax Java will update the rawPath and additional binding values for Operation (based on values received via HttpJsonOperationClient)

For example in Java-Speech generated client:

    this.httpJsonOperationsStub =
        HttpJsonOperationsStub.create(
            clientContext,
            callableFactory,
            typeRegistry,
            ImmutableMap.<String, HttpRule>builder()
                .put(
                    "google.longrunning.Operations.GetOperation",
                    HttpRule.newBuilder().setGet("/v1/operations/{name=**}").build())
                .put(
                    "google.longrunning.Operations.ListOperations",
                    HttpRule.newBuilder().setGet("/v1/operations").build())
                .build());
...

Output:

Running with REST...
Transcript: how old is the Brooklyn Bridge -> No longer errors out

@@ -350,6 +351,9 @@ public class HttpJsonComplianceStub extends ComplianceStub {
.build())
.build();

private static final Map<String, String> operationCustomHttpBindings =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't want to create unused variables if we can avoid it in the generator.

@@ -72,8 +72,14 @@
public class HttpJsonOperationsStub extends OperationsStub {
private static final Pattern CLIENT_PACKAGE_VERSION_PATTERN =
Pattern.compile("\\.(?<version>v\\d+[a-zA-Z]*\\d*[a-zA-Z]*\\d*)\\.[\\w.]*stub");

private static final ApiMethodDescriptor<ListOperationsRequest, ListOperationsResponse>
private static final String LRO_LIST_OPERATIONS = "google.longrunning.Operations.ListOperations";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these all possible operations? Judging from the code in the composer, looks like there could be patch etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

private String getValueBasedOnPatternCase(HttpRule httpRule) {
switch (httpRule.getPatternCase().getNumber()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good if we can provide a reference to the http proto for these mappings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remember to add it in later. I'm not sure if this is the best approach yet, so I haven't been adding any docs or tests yet.

@@ -1103,6 +1143,43 @@ protected List<Expr> createOperationsStubInitExpr(
.build());
}

private Map<String, String> parseCustomHttpBindings(GapicContext context) {
Map<String, String> customHttpBindings = new HashMap<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add all the default operation -> url mapping to this map and override then if needed. Basically we need to move as much code as possible to the generator from gax, as gax is a runtime dependency and it would be much harder to make changes there later.

Copy link
Contributor Author

@lqiu96 lqiu96 Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add the defaults for bunch of mixins (or for only Operation module). I just saw that it is possible to have way more mixins (https://github.com/googleapis/gapic-showcase/blob/b94ecfc51059b49770e5bdb6f0d7ea07903158e8/schema/google/showcase/v1beta1/showcase_v1beta1.yaml#L46-L85) so I didn't want to just create a default list in the gapic-generator that needed to be manually updated later on.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I think that makes sense, we could do it the other way also, construct a map with all the bindings from the yaml, and populate default for the LRO operations. Or if we are confident about the list of mixins, we could pre-populate them as well, a related question, did you get a chance to see how mixins work for location and iam? Are we going to have similar problem for them?

Copy link
Contributor Author

@lqiu96 lqiu96 Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can see, I think only the Operation proto is currently being used. I would imagine if Location and IAM are used, they would be created the same way the Operations Client was in GAX: googleapis/gax-java#1456 and we would reference it via https://github.com/googleapis/google-cloud-java/blob/10bb0cb494f64b864408ede46834e1046351370c/java-speech/google-cloud-speech/src/main/java/com/google/cloud/speech/v1/SpeechClient.java#L164-L166.

I was running under the assumption that they would be added in sometime in the future, but I'm not sure about it. Plus each generated client (operation/ iam/ location) would have the initial defaults set from the proto file: https://github.com/googleapis/gapic-generator-java/blob/d1a16195937df041f65a52717ddf9dc6ceb09b4f/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java#L84

@@ -147,6 +147,12 @@ public Builder<RequestT> setAdditionalPaths(String... rawAdditionalPaths) {
return this;
}

@InternalApi
public Builder<RequestT> updateRawPath(String rawPath) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to initialize the ProtoMessageRequestFormatter with the updated path instead of overriding it later?

Copy link
Contributor Author

@lqiu96 lqiu96 Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ProtoMessageRequestFormatter is created as part of the ApiMethodDescriptor static fields HttpJsonOperationStub: https://github.com/googleapis/gapic-generator-java/blob/b7ca95f12dfe8287c133e09534be1fc46882ce6c/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java#L76-L108

I might be missing something obvious, but I'm not seeing a way to change this value without updating once I get the customHttpBindings map.

}

public static final HttpJsonOperationsStub create(
OperationsStubSettings settings, Map<String, String> customOperationHttpBindings)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is fine but I would try to explore the possibility of add the customOperationHttpBindings to settings or clientContext. It would be cleaner and we don't have to have so many overloaded methods.

Copy link
Contributor Author

@lqiu96 lqiu96 Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(From what I understand) The core of the logic which we can change in the generated library comes from:

  protected HttpJsonSpeechStub(
      SpeechStubSettings settings,
      ClientContext clientContext,
      HttpJsonStubCallableFactory callableFactory)
      throws IOException {
    this.callableFactory = callableFactory;
    this.httpJsonOperationsStub =
        HttpJsonOperationsStub.create(
            clientContext,
            callableFactory,
            typeRegistry,
...

at https://github.com/googleapis/google-cloud-java/blob/9c0ddf2879b0e3ddf29b5d67cd44f0336f492134/java-speech/google-cloud-speech/src/main/java/com/google/cloud/speech/v1/stub/HttpJsonSpeechStub.java#L181-L188

The SpeechStubSettings settings parameter is different from OperationsStubSettings which is what HttpJsonOperationsStub.create() is expecting. The other approach I'm looking at is to try and add it either as part of the ClientContext (currently leaning towards not putting it here as it doesn't seem to be related) or the HttpJsonStubCallableFactory (Need to do a bit more digging into this).

The overloaded methods were the easiest way I could get the CI builds to pass. Ideally, I would just have each of the .create() methods take in another Map parameter (and not to duplicate the .create() methods), but I'm having issue with the build passing. I can show you this tomorrow.

meltsufin and others added 10 commits February 23, 2023 12:42
…downstream test (#1291)

* ci: use java-shared-dependencies in google-cloud-java

* No need to modify google-cloud-jar-parent
…figurations for netty classes (#1290)

* fix(java): initialize netty-shaded at run-time and add reflection configurations for netty classes
* ci(showcase): disable rest_numeric_enum feature in showcase tests
Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Pin Bazel version to 5.2.0 as googleapis already updated to 5.2.0
Bumps [cryptography](https://github.com/pyca/cryptography) from 38.0.3 to 39.0.1.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](pyca/cryptography@38.0.3...39.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mridula <66699525+mpeddada1@users.noreply.github.com>
* chore: (cleanup) removing unused files

* chore: working on comments
…a-common-protos, and java-iam (#1305)

The build is Maven or Bazel. The Gradle files in the recently migrated repositories (api-common-java, gax-java, java-common-protos, and java-iam) are not used.

Note that this pull request is not touching rules_java_gapic/resources/gradle which is still used to generate Gradle files for self-service client libraries.
- selector: google.longrunning.Operations.ListOperations
get: '/v1beta1/operations'
additional_bindings:
- get: '/v1/operations'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have multiple additional bindings here? If yes, can we add more here to cover the test case?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, do you know how additional_bindings are used in LROs or is it even used? If not, we probably can get rid of the duplicated logics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'll add more bindings for this. Thanks!

Copy link
Contributor Author

@lqiu96 lqiu96 Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, do you know how additional_bindings are used in LROs or is it even used? If not, we probably can get rid of the duplicated logics.

I've actually just been doing a bit of digging into as I've been running into some showcase issues with additional_bindings. For httpjson, I don't think there currently is any logic to handles checking other bindings once the first one doesn't match: https://github.com/googleapis/gapic-generator-java/blob/c23f981e2ac3c573bed51e725dc7061551179400/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestRunnable.java#L184

I don't see any logic that handles checking the other paths/ additional_bindings which I think is a bug. For grpc, I'll need to do some more digging. It doesn't seem like grpc uses the paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed a bug here: #1440

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Lawrence, that bug looks generic for all additional_bindings, anything special for the OperationClient? Or as long as it's REST, there is no differences between OperationClient and other clients?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the bug is generic for all additional_bindings and it's a very specific edge case.

There is nothing special for OperationsClient, but I just added multiple additional_bindings to the Operations Client for showcase.

import org.junit.BeforeClass;
import org.junit.Test;

public class HttpJsonOperationsStubTest {
Copy link
Collaborator

@blakeli0 blakeli0 Mar 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can probably use some refactoring. One best practice for sharing test object is that we can create it every time in setUp and reference the object later, similar to this test file.

Copy link
Contributor Author

@lqiu96 lqiu96 Mar 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had an HttpJsonOperationStub object inside this test, but the method to get the MethodDescriptors is static: https://github.com/googleapis/gapic-generator-java/blob/0ccf457a774bd3dbe3b101d82222bdc39f6e5b0e/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/longrunning/stub/HttpJsonOperationsStub.java#L331 and I had to invoke it with HttpJsonOperationStub.getMethodDescriptors().

Is there a way to test this so that I can create an Stub object in the test and then reference it in my tests?

.getAdditionalPathTemplates()
.get(0)
.toRawString())
.isEqualTo("testList2");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verifying a value that was set up in setUp method is also not a good practice, see here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test tries to cover the updateDefaultApiMethodDescriptors function. Perhaps the easier/ cleaner way for this is to just open method as default scope and test it that way?

@@ -366,7 +368,24 @@ protected HttpJsonEchoStub(
throws IOException {
this.callableFactory = callableFactory;
this.httpJsonOperationsStub =
HttpJsonOperationsStub.create(clientContext, callableFactory, typeRegistry);
HttpJsonOperationsStub.create(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the actual showcase integration tests that verified the interactions will be done in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going forward, the interactions will be covered in that PR. For this PR, I just generated the speech client locally and tested that Operations works.

case 5:
return httpRule.getDelete();
default:
throw new IllegalArgumentException(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably write some true unit tests for this method and the method above, as the input and output are very easy to mock and verify, but I know they are mostly already covered in the golden unit tests, so as long as we covered all the cases, it's fine to me.
However, this branch couldn't be covered by golden tests, so we should add a unit test for it(by changing the scope of this method to default).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this branch couldn't be covered by golden tests, so we should add a unit test for it

I'm a bit lost of this part. The Echo goldens for rest should have it right?
https://github.com/googleapis/gapic-generator-java/blob/88487e38f86494e2ba18d4bf7c7f23c6671aeb24/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden#L360-L405

@sonarcloud
Copy link

sonarcloud bot commented Mar 9, 2023

Please retry analysis of this Pull-Request directly on SonarCloud.

@@ -58,6 +60,13 @@ public GapicContext parseShowcaseEcho() {
ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0);
assertEquals("Echo", echoServiceDescriptor.getName());

String serviceYamlFileName = "showcase_v1beta1.yaml";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably missed this in previous reviews, instead of showcase_v1beta1.yaml, can we rename it to reflect the fact that this yaml is only used by Echo at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, can rename to echo_v1beta.yaml. We can rename it back to showcase_v1beta1.yaml if other tests need to use this file again for other showcase protos.

.build();
/* This function returns the list of method descriptors (custom or default) */
@InternalApi
public List<ApiMethodDescriptor> getAllMethodDescriptors() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make remove the public here since it's only used in tests, we should also make that clear in the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockHttpService requires this in OperationsClientTest. I'll add @VisibleForTesting annotation here to mark its intended use case.

if (apiVersion == null) {
return methodDescriptor;
/* OperationsClient's RPCs are mapped to GET/POST/DELETE and this function only expects those HttpVerbs to be used */
private static String getValueBasedOnPatternCase(HttpRule httpRule) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does not have to be static now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

List<ApiMethodDescriptor> apiMethodDescriptorList =
httpJsonOperationsStub.getAllMethodDescriptors();
assertThat(apiMethodDescriptorList.get(0).getRequestFormatter().getPathTemplate().toRawString())
.isEqualTo("testList");
Copy link
Collaborator

@blakeli0 blakeli0 Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could extract this string to a variable in the method or static field in the test class, since they are reused in the setup and verification. Same thing for the test below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

@blakeli0 blakeli0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sonarcloud
Copy link

sonarcloud bot commented Mar 10, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 5 Code Smells

96.6% 96.6% Coverage
0.0% 0.0% Duplication

@lqiu96 lqiu96 added the automerge Merge the pull request once unit tests and other checks pass. label Mar 10, 2023
@lqiu96 lqiu96 merged commit f8ccd2a into main Mar 10, 2023
@lqiu96 lqiu96 deleted the main-fix_custom_LRO_httpbindings branch March 10, 2023 22:35
@gcf-merge-on-green gcf-merge-on-green bot removed the automerge Merge the pull request once unit tests and other checks pass. label Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: xl Pull request size is extra large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

REST LRO fails for certain calls