go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tq/submitter.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tq 16 17 import ( 18 "context" 19 "time" 20 21 cloudtasks "cloud.google.com/go/cloudtasks/apiv2" 22 pubsub "cloud.google.com/go/pubsub/apiv1" 23 "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 24 "google.golang.org/api/option" 25 "google.golang.org/grpc" 26 "google.golang.org/grpc/codes" 27 "google.golang.org/grpc/credentials" 28 "google.golang.org/grpc/keepalive" 29 "google.golang.org/grpc/status" 30 31 "go.chromium.org/luci/common/errors" 32 "go.chromium.org/luci/grpc/grpcmon" 33 34 "go.chromium.org/luci/server/tq/internal/reminder" 35 ) 36 37 // Submitter is used by Dispatcher to submit tasks. 38 // 39 // It lives in the context, so that it can be mocked in tests. In production 40 // contexts (setup when using the tq server module), the submitter is 41 // initialized to be CloudSubmitter. Tests will need to provide their own 42 // submitter (usually via TestingContext). 43 // 44 // Note that currently Submitter can only be implemented by structs in server/tq 45 // package, since its signature depends on an internal reminder.Payload type. 46 type Submitter interface { 47 // Submit submits a task, returning a gRPC status. 48 // 49 // AlreadyExists status indicates the task with request name already exists. 50 // Other statuses are handled using their usual semantics. 51 // 52 // Will be called from multiple goroutines at once. 53 Submit(ctx context.Context, p *reminder.Payload) error 54 } 55 56 // CloudSubmitter implements Submitter on top of Google Cloud APIs. 57 type CloudSubmitter struct { 58 tasks *cloudtasks.Client 59 pubsub *pubsub.PublisherClient 60 } 61 62 // NewCloudSubmitter creates a new submitter. 63 func NewCloudSubmitter(ctx context.Context, creds credentials.PerRPCCredentials) (*CloudSubmitter, error) { 64 // gRPC options used for both Cloud Tasks and PubSub clients. Copy-pasted from 65 // cloud.google.com/go/pubsub initialization. 66 opts := []option.ClientOption{ 67 option.WithGRPCDialOption(grpc.WithKeepaliveParams(keepalive.ClientParameters{ 68 Time: 5 * time.Minute, 69 })), 70 option.WithGRPCDialOption(grpc.WithStatsHandler(&grpcmon.ClientRPCStatsMonitor{})), 71 option.WithGRPCDialOption(grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor())), 72 option.WithGRPCDialOption(grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor())), 73 option.WithGRPCDialOption(grpc.WithPerRPCCredentials(creds)), 74 } 75 76 tasks, err := cloudtasks.NewClient(ctx, opts...) 77 if err != nil { 78 return nil, errors.Annotate(err, "failed to initialize Cloud Tasks client").Err() 79 } 80 81 pubsub, err := pubsub.NewPublisherClient(ctx, opts...) 82 if err != nil { 83 tasks.Close() 84 return nil, errors.Annotate(err, "failed to initialize Cloud PubSub client").Err() 85 } 86 87 return &CloudSubmitter{tasks: tasks, pubsub: pubsub}, nil 88 } 89 90 // Close closes the submitter. 91 func (s *CloudSubmitter) Close() { 92 s.tasks.Close() 93 s.pubsub.Close() 94 } 95 96 // Submit creates a task, returning a gRPC status. 97 func (s *CloudSubmitter) Submit(ctx context.Context, p *reminder.Payload) (err error) { 98 switch { 99 case p.CreateTaskRequest != nil: 100 _, err = s.tasks.CreateTask(ctx, p.CreateTaskRequest) 101 case p.PublishRequest != nil: 102 _, err = s.pubsub.Publish(ctx, p.PublishRequest) 103 default: 104 err = status.Errorf(codes.Internal, "unrecognized payload kind") 105 } 106 return 107 } 108 109 var submitterCtxKey = "go.chromium.org/luci/server/tq.Submitter" 110 111 // UseSubmitter puts an arbitrary submitter in the context. 112 // 113 // It will be used by Dispatcher's AddTask to submit Cloud Tasks. 114 func UseSubmitter(ctx context.Context, s Submitter) context.Context { 115 return context.WithValue(ctx, &submitterCtxKey, s) 116 } 117 118 // currentSubmitter returns the Submitter in the context or an error. 119 func currentSubmitter(ctx context.Context) (Submitter, error) { 120 sub, _ := ctx.Value(&submitterCtxKey).(Submitter) 121 if sub == nil { 122 return nil, errors.New("not a valid TQ context, no Submitter available") 123 } 124 return sub, nil 125 }