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  }