go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/recorder/recorder.go (about) 1 // Copyright 2019 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 recorder 16 17 import ( 18 "context" 19 "time" 20 21 sppb "cloud.google.com/go/spanner/apiv1/spannerpb" 22 repb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2" 23 "google.golang.org/genproto/googleapis/bytestream" 24 "google.golang.org/grpc" 25 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/common/logging" 28 "go.chromium.org/luci/grpc/prpc" 29 "go.chromium.org/luci/server" 30 31 "go.chromium.org/luci/resultdb/internal" 32 "go.chromium.org/luci/resultdb/internal/artifactcontent" 33 "go.chromium.org/luci/resultdb/internal/services/artifactexporter" 34 "go.chromium.org/luci/resultdb/internal/spanutil" 35 pb "go.chromium.org/luci/resultdb/proto/v1" 36 ) 37 38 // recorderServer implements pb.RecorderServer. 39 // 40 // It does not return gRPC-native errors; use DecoratedRecorder with 41 // internal.CommonPostlude. 42 type recorderServer struct { 43 *Options 44 45 // casClient is an instance of ContentAddressableStorageClient which is used for 46 // artifact batch operations. 47 casClient repb.ContentAddressableStorageClient 48 49 // bqExportClient is used for exporting artifacts to BigQuery. 50 bqExportClient BQExportClient 51 } 52 53 // Options is recorder server configuration. 54 type Options struct { 55 // Duration since invocation creation after which to delete expected test 56 // results. 57 ExpectedResultsExpiration time.Duration 58 59 // ArtifactRBEInstance is the name of the RBE instance to use for artifact 60 // storage. Example: "projects/luci-resultdb/instances/artifacts". 61 ArtifactRBEInstance string 62 63 // MaxArtifactContentStreamLength is the maximum size of an artifact content 64 // to accept via streaming API. 65 MaxArtifactContentStreamLength int64 66 } 67 68 // InitServer initializes a recorder server. 69 func InitServer(srv *server.Server, opt Options) error { 70 conn, err := artifactcontent.RBEConn(srv.Context) 71 if err != nil { 72 return err 73 } 74 bqClient, err := artifactexporter.NewClient(srv.Context, srv.Options.CloudProject) 75 if err != nil { 76 return errors.Annotate(err, "create bq export client").Err() 77 } 78 srv.RegisterCleanup(func(ctx context.Context) { 79 err := bqClient.Close() 80 if err != nil { 81 logging.Errorf(ctx, "Cleaning up BigQuery export client: %s", err) 82 } 83 }) 84 85 pb.RegisterRecorderServer(srv, &pb.DecoratedRecorder{ 86 Service: &recorderServer{ 87 Options: &opt, 88 casClient: repb.NewContentAddressableStorageClient(conn), 89 bqExportClient: bqClient, 90 }, 91 Postlude: internal.CommonPostlude, 92 }) 93 94 // TODO(crbug/1082369): Remove this workaround once field masks can be decoded. 95 srv.ConfigurePRPC(func(p *prpc.Server) { 96 p.HackFixFieldMasksForJSON = true 97 }) 98 99 srv.RegisterUnaryServerInterceptors(spanutil.SpannerDefaultsInterceptor(sppb.RequestOptions_PRIORITY_HIGH)) 100 return installArtifactCreationHandler(srv, &opt, conn) 101 } 102 103 // installArtifactCreationHandler installs artifact creation handler. 104 func installArtifactCreationHandler(srv *server.Server, opt *Options, rbeConn *grpc.ClientConn) error { 105 if opt.ArtifactRBEInstance == "" { 106 return errors.Reason("opt.ArtifactRBEInstance is required").Err() 107 } 108 109 bs := bytestream.NewByteStreamClient(rbeConn) 110 ach := &artifactCreationHandler{ 111 RBEInstance: opt.ArtifactRBEInstance, 112 NewCASWriter: func(ctx context.Context) (bytestream.ByteStream_WriteClient, error) { 113 return bs.Write(ctx) 114 }, 115 MaxArtifactContentStreamLength: opt.MaxArtifactContentStreamLength, 116 } 117 118 // Ideally we define more specific routes, but 119 // "github.com/julienschmidt/httprouter" does not support routes over 120 // unescaped paths: https://github.com/julienschmidt/httprouter/issues/208 121 srv.Routes.PUT("invocations/*rest", nil, ach.Handle) 122 return nil 123 }