Skip to content

Commit edb3b8f

Browse files
authoredSep 21, 2023
fix(internal/gengapic): add workaround for operation collision (#1397)
We have a service that is not yet released that uses the same RPC name across two services in the same proto package. Our Operation wrapper identifiers are not scoped to a service so this causes issues when generated a client that does this. For now this is for one client, which may change before GA, so a hacky solution is fine. But we should keep this in the back of our minds should we ever v2 our libraries this is something we would want to avoid.
1 parent 845a130 commit edb3b8f

File tree

6 files changed

+243
-9
lines changed

6 files changed

+243
-9
lines changed
 

‎internal/gengapic/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ go_test(
9999
"@org_golang_google_protobuf//encoding/protojson",
100100
"@org_golang_google_protobuf//proto",
101101
"@org_golang_google_protobuf//reflect/protodesc",
102+
"@org_golang_google_protobuf//reflect/protoreflect",
102103
"@org_golang_google_protobuf//runtime/protoiface",
103104
"@org_golang_google_protobuf//types/descriptorpb",
104105
"@org_golang_google_protobuf//types/known/apipb",

‎internal/gengapic/client_init.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ func (g *generator) internalClientIntfInit(serv *descriptor.ServiceDescriptorPro
116116
case g.isLRO(m):
117117
// Unary call where the return type is a wrapper of
118118
// longrunning.Operation and more precise types
119-
lroType := lroTypeName(m.GetName())
119+
lroType := lroTypeName(m)
120120
p("%s(context.Context, *%s.%s, ...gax.CallOption) (*%s, error)",
121-
m.GetName(), inSpec.Name, inType.GetName(), lroTypeName(m.GetName()))
121+
m.GetName(), inSpec.Name, inType.GetName(), lroType)
122122
p("%[1]s(name string) *%[1]s", lroType)
123123

124124
case m.GetClientStreaming():
@@ -258,7 +258,7 @@ func (g *generator) genClientWrapperMethod(m *descriptor.MethodDescriptorProto,
258258

259259
if g.isLRO(m) {
260260
reqTyp := fmt.Sprintf("%s.%s", inSpec.Name, inType.GetName())
261-
lroType := lroTypeName(m.GetName())
261+
lroType := lroTypeName(m)
262262
p("func (c *%s) %s(ctx context.Context, req *%s, opts ...gax.CallOption) (*%s, error) {",
263263
clientTypeName, m.GetName(), reqTyp, lroType)
264264
p(" return c.internalClient.%s(ctx, req, opts...)", m.GetName())

‎internal/gengapic/client_init_test.go

+44-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
code "google.golang.org/genproto/googleapis/rpc/code"
3434
"google.golang.org/protobuf/encoding/protojson"
3535
"google.golang.org/protobuf/proto"
36+
"google.golang.org/protobuf/reflect/protoreflect"
3637
"google.golang.org/protobuf/runtime/protoiface"
3738
"google.golang.org/protobuf/types/known/apipb"
3839
duration "google.golang.org/protobuf/types/known/durationpb"
@@ -361,7 +362,6 @@ func TestClientInit(t *testing.T) {
361362
}
362363

363364
customOpOpts := &descriptor.MethodOptions{}
364-
proto.SetExtension(customOpOpts, extendedops.E_OperationService, opS.GetName())
365365
servCustomOp := &descriptor.ServiceDescriptorProto{
366366
Name: proto.String("Foo"),
367367
Method: []*descriptor.MethodDescriptorProto{
@@ -390,6 +390,7 @@ func TestClientInit(t *testing.T) {
390390
parameter *string
391391
imports map[pbinfo.ImportSpec]bool
392392
wantNumSnps int
393+
setExt func() (protoreflect.ExtensionType, interface{})
393394
}{
394395
{
395396
tstName: "foo_client_init",
@@ -498,8 +499,50 @@ func TestClientInit(t *testing.T) {
498499
{Name: "httptransport", Path: "google.golang.org/api/transport/http"}: true,
499500
},
500501
wantNumSnps: 1,
502+
setExt: func() (protoreflect.ExtensionType, interface{}) {
503+
return extendedops.E_OperationService, opS.GetName()
504+
},
505+
},
506+
{
507+
tstName: "lro_client_conflict",
508+
mixins: mixins{
509+
"google.longrunning.Operations": operationsMethods(),
510+
},
511+
servName: "Foo",
512+
serv: servLRO,
513+
parameter: proto.String("go-gapic-package=path;mypackage"),
514+
imports: map[pbinfo.ImportSpec]bool{
515+
{Name: "gtransport", Path: "google.golang.org/api/transport/grpc"}: true,
516+
{Name: "longrunningpb", Path: "cloud.google.com/go/longrunning/autogen/longrunningpb"}: true,
517+
{Name: "lroauto", Path: "cloud.google.com/go/longrunning/autogen"}: true,
518+
{Name: "mypackagepb", Path: "github.com/googleapis/mypackage"}: true,
519+
{Path: "context"}: true,
520+
{Path: "google.golang.org/api/option"}: true,
521+
{Path: "google.golang.org/grpc"}: true,
522+
},
523+
wantNumSnps: 6,
524+
setExt: func() (protoreflect.ExtensionType, interface{}) {
525+
return annotations.E_Http, &annotations.HttpRule{
526+
Pattern: &annotations.HttpRule_Post{
527+
Post: "/v1beta1/{parent=projects/*/locations/*/featureGroups/*}/features",
528+
},
529+
}
530+
},
501531
},
502532
} {
533+
setExt := tst.setExt
534+
if setExt == nil {
535+
setExt = func() (protoreflect.ExtensionType, interface{}) {
536+
return annotations.E_Http, &annotations.HttpRule{
537+
Pattern: &annotations.HttpRule_Get{
538+
Get: "/zip",
539+
},
540+
}
541+
}
542+
}
543+
ext, value := setExt()
544+
proto.SetExtension(tst.serv.Method[0].GetOptions(), ext, value)
545+
503546
fds := append(mixinDescriptors(), &descriptor.FileDescriptorProto{
504547
Package: proto.String("mypackage"),
505548
Options: &descriptor.FileOptions{

‎internal/gengapic/genrest.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ func (g *generator) lroRESTCall(servName string, m *descriptor.MethodDescriptorP
909909
}
910910
g.imports[outSpec] = true
911911

912-
opWrapperType := lroTypeName(m.GetName())
912+
opWrapperType := lroTypeName(m)
913913
p("func (c *%s) %s(ctx context.Context, req *%s.%s, opts ...gax.CallOption) (*%s, error) {",
914914
lowcaseServName, m.GetName(), inSpec.Name, inType.GetName(), opWrapperType)
915915

‎internal/gengapic/lro.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
longrunning "cloud.google.com/go/longrunning/autogen/longrunningpb"
2222
"github.com/golang/protobuf/protoc-gen-go/descriptor"
2323
"github.com/googleapis/gapic-generator-go/internal/pbinfo"
24+
"google.golang.org/genproto/googleapis/api/annotations"
2425
"google.golang.org/protobuf/proto"
2526
)
2627

@@ -38,7 +39,7 @@ func (g *generator) lroCall(servName string, m *descriptor.MethodDescriptorProto
3839
return err
3940
}
4041

41-
lroType := lroTypeName(m.GetName())
42+
lroType := lroTypeName(m)
4243
p := g.printf
4344

4445
lowcaseServName := lowerFirst(servName + "GRPCClient")
@@ -74,7 +75,7 @@ func (g *generator) lroCall(servName string, m *descriptor.MethodDescriptorProto
7475
func (g *generator) lroType(servName string, serv *descriptor.ServiceDescriptorProto, m *descriptor.MethodDescriptorProto) error {
7576
protoPkg := g.descInfo.ParentFile[serv].GetPackage()
7677
mFQN := fmt.Sprintf("%s.%s.%s", protoPkg, serv.GetName(), m.GetName())
77-
lroType := lroTypeName(m.GetName())
78+
lroType := lroTypeName(m)
7879
p := g.printf
7980
hasREST := containsTransport(g.opts.transports, rest)
8081

@@ -280,6 +281,17 @@ func (g *generator) lroType(servName string, serv *descriptor.ServiceDescriptorP
280281
return nil
281282
}
282283

283-
func lroTypeName(methodName string) string {
284-
return methodName + "Operation"
284+
func lroTypeName(m *descriptor.MethodDescriptorProto) string {
285+
// This whole if block is a hack to workaround a operation handler namespace
286+
// collision. We should remove this in the future if the design is fixed for
287+
// the v1 api. This is for aiplatform.featureregistryservice.createfeature.
288+
if eHTTP, ok := proto.GetExtension(m.GetOptions(), annotations.E_Http).(*annotations.HttpRule); ok && eHTTP != nil && eHTTP.Pattern != nil {
289+
switch t := eHTTP.Pattern.(type) {
290+
case *annotations.HttpRule_Post:
291+
if t.Post == "/v1beta1/{parent=projects/*/locations/*/featureGroups/*}/features" {
292+
return m.GetName() + "RegistryOperation"
293+
}
294+
}
295+
}
296+
return m.GetName() + "Operation"
285297
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// internalFooClient is an interface that defines the methods available from Awesome Foo API.
2+
type internalFooClient interface {
3+
Close() error
4+
setGoogleClientInfo(...string)
5+
Connection() *grpc.ClientConn
6+
Zip(context.Context, *mypackagepb.Bar, ...gax.CallOption) (*ZipRegistryOperation, error)
7+
ZipRegistryOperation(name string) *ZipRegistryOperation
8+
ListOperations(context.Context, *longrunningpb.ListOperationsRequest, ...gax.CallOption) *OperationIterator
9+
GetOperation(context.Context, *longrunningpb.GetOperationRequest, ...gax.CallOption) (*longrunningpb.Operation, error)
10+
DeleteOperation(context.Context, *longrunningpb.DeleteOperationRequest, ...gax.CallOption) error
11+
CancelOperation(context.Context, *longrunningpb.CancelOperationRequest, ...gax.CallOption) error
12+
WaitOperation(context.Context, *longrunningpb.WaitOperationRequest, ...gax.CallOption) (*longrunningpb.Operation, error)
13+
}
14+
15+
// FooClient is a client for interacting with Awesome Foo API.
16+
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
17+
//
18+
// Foo service does stuff.
19+
type FooClient struct {
20+
// The internal transport-dependent client.
21+
internalClient internalFooClient
22+
23+
// The call options for this service.
24+
CallOptions *FooCallOptions
25+
26+
// LROClient is used internally to handle long-running operations.
27+
// It is exposed so that its CallOptions can be modified if required.
28+
// Users should not Close this client.
29+
LROClient *lroauto.OperationsClient
30+
31+
}
32+
33+
// Wrapper methods routed to the internal client.
34+
35+
// Close closes the connection to the API service. The user should invoke this when
36+
// the client is no longer required.
37+
func (c *FooClient) Close() error {
38+
return c.internalClient.Close()
39+
}
40+
41+
// setGoogleClientInfo sets the name and version of the application in
42+
// the `x-goog-api-client` header passed on each request. Intended for
43+
// use by Google-written clients.
44+
func (c *FooClient) setGoogleClientInfo(keyval ...string) {
45+
c.internalClient.setGoogleClientInfo(keyval...)
46+
}
47+
48+
// Connection returns a connection to the API service.
49+
//
50+
// Deprecated: Connections are now pooled so this method does not always
51+
// return the same resource.
52+
func (c *FooClient) Connection() *grpc.ClientConn {
53+
return c.internalClient.Connection()
54+
}
55+
56+
// Zip does some stuff.
57+
func (c *FooClient) Zip(ctx context.Context, req *mypackagepb.Bar, opts ...gax.CallOption) (*ZipRegistryOperation, error) {
58+
return c.internalClient.Zip(ctx, req, opts...)
59+
}
60+
61+
// ZipRegistryOperation returns a new ZipRegistryOperation from a given name.
62+
// The name must be that of a previously created ZipRegistryOperation, possibly from a different process.
63+
func (c *FooClient) ZipRegistryOperation(name string) *ZipRegistryOperation {
64+
return c.internalClient.ZipRegistryOperation(name)
65+
}
66+
67+
func (c *FooClient) ListOperations(ctx context.Context, req *longrunningpb.ListOperationsRequest, opts ...gax.CallOption) *OperationIterator {
68+
return c.internalClient.ListOperations(ctx, req, opts...)
69+
}
70+
71+
func (c *FooClient) GetOperation(ctx context.Context, req *longrunningpb.GetOperationRequest, opts ...gax.CallOption) (*longrunningpb.Operation, error) {
72+
return c.internalClient.GetOperation(ctx, req, opts...)
73+
}
74+
75+
func (c *FooClient) DeleteOperation(ctx context.Context, req *longrunningpb.DeleteOperationRequest, opts ...gax.CallOption) error {
76+
return c.internalClient.DeleteOperation(ctx, req, opts...)
77+
}
78+
79+
func (c *FooClient) CancelOperation(ctx context.Context, req *longrunningpb.CancelOperationRequest, opts ...gax.CallOption) error {
80+
return c.internalClient.CancelOperation(ctx, req, opts...)
81+
}
82+
83+
func (c *FooClient) WaitOperation(ctx context.Context, req *longrunningpb.WaitOperationRequest, opts ...gax.CallOption) (*longrunningpb.Operation, error) {
84+
return c.internalClient.WaitOperation(ctx, req, opts...)
85+
}
86+
87+
// fooGRPCClient is a client for interacting with Awesome Foo API over gRPC transport.
88+
//
89+
// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls.
90+
type fooGRPCClient struct {
91+
// Connection pool of gRPC connections to the service.
92+
connPool gtransport.ConnPool
93+
94+
// Points back to the CallOptions field of the containing FooClient
95+
CallOptions **FooCallOptions
96+
97+
// The gRPC API client.
98+
fooClient mypackagepb.FooClient
99+
100+
// LROClient is used internally to handle long-running operations.
101+
// It is exposed so that its CallOptions can be modified if required.
102+
// Users should not Close this client.
103+
LROClient **lroauto.OperationsClient
104+
105+
operationsClient longrunningpb.OperationsClient
106+
107+
// The x-goog-* metadata to be sent with each request.
108+
xGoogHeaders []string
109+
}
110+
111+
// NewFooClient creates a new foo client based on gRPC.
112+
// The returned client must be Closed when it is done being used to clean up its underlying connections.
113+
//
114+
// Foo service does stuff.
115+
func NewFooClient(ctx context.Context, opts ...option.ClientOption) (*FooClient, error) {
116+
clientOpts := defaultFooGRPCClientOptions()
117+
if newFooClientHook != nil {
118+
hookOpts, err := newFooClientHook(ctx, clientHookParams{})
119+
if err != nil {
120+
return nil, err
121+
}
122+
clientOpts = append(clientOpts, hookOpts...)
123+
}
124+
125+
connPool, err := gtransport.DialPool(ctx, append(clientOpts, opts...)...)
126+
if err != nil {
127+
return nil, err
128+
}
129+
client := FooClient{CallOptions: defaultFooCallOptions()}
130+
131+
c := &fooGRPCClient{
132+
connPool: connPool,
133+
fooClient: mypackagepb.NewFooClient(connPool),
134+
CallOptions: &client.CallOptions,
135+
operationsClient: longrunningpb.NewOperationsClient(connPool),
136+
137+
}
138+
c.setGoogleClientInfo()
139+
140+
client.internalClient = c
141+
142+
client.LROClient, err = lroauto.NewOperationsClient(ctx, gtransport.WithConnPool(connPool))
143+
if err != nil {
144+
// This error "should not happen", since we are just reusing old connection pool
145+
// and never actually need to dial.
146+
// If this does happen, we could leak connp. However, we cannot close conn:
147+
// If the user invoked the constructor with option.WithGRPCConn,
148+
// we would close a connection that's still in use.
149+
// TODO: investigate error conditions.
150+
return nil, err
151+
}
152+
c.LROClient = &client.LROClient
153+
return &client, nil
154+
}
155+
156+
// Connection returns a connection to the API service.
157+
//
158+
// Deprecated: Connections are now pooled so this method does not always
159+
// return the same resource.
160+
func (c *fooGRPCClient) Connection() *grpc.ClientConn {
161+
return c.connPool.Conn()
162+
}
163+
164+
// setGoogleClientInfo sets the name and version of the application in
165+
// the `x-goog-api-client` header passed on each request. Intended for
166+
// use by Google-written clients.
167+
func (c *fooGRPCClient) setGoogleClientInfo(keyval ...string) {
168+
kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
169+
kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "grpc", grpc.Version)
170+
c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
171+
}
172+
173+
// Close closes the connection to the API service. The user should invoke this when
174+
// the client is no longer required.
175+
func (c *fooGRPCClient) Close() error {
176+
return c.connPool.Close()
177+
}
178+

0 commit comments

Comments
 (0)
Please sign in to comment.