go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/app/commitverifier.go (about) 1 // Copyright 2022 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 app 16 17 import ( 18 "context" 19 "encoding/json" 20 "net/http" 21 22 "google.golang.org/protobuf/encoding/protojson" 23 24 "go.chromium.org/luci/common/errors" 25 "go.chromium.org/luci/common/tsmon/field" 26 "go.chromium.org/luci/common/tsmon/metric" 27 cvv1 "go.chromium.org/luci/cv/api/v1" 28 "go.chromium.org/luci/server/router" 29 30 "go.chromium.org/luci/analysis/internal/ingestion/join" 31 ) 32 33 var ( 34 cvRunCounter = metric.NewCounter( 35 "analysis/ingestion/pubsub/cv_runs", 36 "The number of CV runs received by LUCI Analysis from PubSub.", 37 nil, 38 // The LUCI Project. 39 field.String("project"), 40 // "success", "transient-failure", "permanent-failure" or "ignored". 41 field.String("status")) 42 ) 43 44 type handleCVRunMethod func(ctx context.Context, psRun *cvv1.PubSubRun) (project string, processed bool, err error) 45 46 // CVRunHandler accepts and processes CV Run Pub/Sub messages. 47 type CVRunHandler struct { 48 // The method to use to handle the deserialized CV Run pub/sub message. 49 // Used to allow the handler to be replaced for testing. 50 handleCVRun handleCVRunMethod 51 } 52 53 // NewCVRunHandler initialises a new CVRunHandler. 54 func NewCVRunHandler() *CVRunHandler { 55 return &CVRunHandler{ 56 handleCVRun: join.JoinCVRun, 57 } 58 } 59 60 // Handle processes a CV Pub/Sub message. 61 func (h *CVRunHandler) Handle(ctx *router.Context) { 62 status := "unknown" 63 project := "unknown" 64 defer func() { 65 // Closure for late binding. 66 cvRunCounter.Add(ctx.Request.Context(), 1, project, status) 67 }() 68 project, processed, err := h.handlerImpl(ctx.Request.Context(), ctx.Request) 69 70 switch { 71 case err != nil: 72 errors.Log(ctx.Request.Context(), errors.Annotate(err, "handling cv pubsub event").Err()) 73 status = processErr(ctx, err) 74 return 75 case !processed: 76 status = "ignored" 77 // Use subtly different "success" response codes to surface in 78 // standard GAE logs whether an ingestion was ignored or not, 79 // while still acknowledging the pub/sub. 80 // See https://cloud.google.com/pubsub/docs/push#receiving_messages. 81 ctx.Writer.WriteHeader(http.StatusNoContent) // 204 82 default: 83 status = "success" 84 ctx.Writer.WriteHeader(http.StatusOK) 85 } 86 } 87 88 func (h *CVRunHandler) handlerImpl(ctx context.Context, request *http.Request) (project string, processed bool, err error) { 89 psRun, err := extractPubSubRun(request) 90 if err != nil { 91 return "unknown", false, errors.Annotate(err, "failed to extract run").Err() 92 } 93 94 return h.handleCVRun(ctx, psRun) 95 } 96 97 func extractPubSubRun(r *http.Request) (*cvv1.PubSubRun, error) { 98 var msg pubsubMessage 99 if err := json.NewDecoder(r.Body).Decode(&msg); err != nil { 100 return nil, errors.Annotate(err, "could not decode cv pubsub message").Err() 101 } 102 103 var run cvv1.PubSubRun 104 err := protojson.Unmarshal(msg.Message.Data, &run) 105 if err != nil { 106 return nil, errors.Annotate(err, "could not parse cv pubsub message data").Err() 107 } 108 return &run, nil 109 }