go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/culpritaction/revertculprit/fallbackactions.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 revertculprit 16 17 import ( 18 "context" 19 "fmt" 20 21 "go.chromium.org/luci/bisection/internal/gerrit" 22 "go.chromium.org/luci/bisection/internal/rotationproxy" 23 "go.chromium.org/luci/bisection/model" 24 pb "go.chromium.org/luci/bisection/proto/v1" 25 26 "go.chromium.org/luci/common/clock" 27 "go.chromium.org/luci/common/errors" 28 "go.chromium.org/luci/common/logging" 29 gerritpb "go.chromium.org/luci/common/proto/gerrit" 30 "go.chromium.org/luci/gae/service/datastore" 31 ) 32 33 func commentSupportOnExistingRevert(ctx context.Context, gerritClient *gerrit.Client, 34 culpritModel *model.Suspect, revert *gerritpb.ChangeInfo) error { 35 lbOwned, err := gerrit.IsOwnedByLUCIBisection(ctx, revert) 36 if err != nil { 37 return errors.Annotate(err, 38 "failed handling existing revert when finding owner").Err() 39 } 40 41 if lbOwned { 42 // Revert is owned by LUCI Bisection - no further action required 43 saveInactionReason(ctx, culpritModel, pb.CulpritInactionReason_REVERT_OWNED_BY_BISECTION) 44 return nil 45 } 46 47 // Revert is not owned by LUCI Bisection 48 lbCommented, err := gerrit.HasLUCIBisectionComment(ctx, revert) 49 if err != nil { 50 return errors.Annotate(err, 51 "failed handling existing revert when checking for pre-existing comment").Err() 52 } 53 54 if lbCommented { 55 // Revert already has a comment by LUCI Bisection - no further action 56 // required 57 saveInactionReason(ctx, culpritModel, pb.CulpritInactionReason_REVERT_HAS_COMMENT) 58 return nil 59 } 60 var message string 61 switch culpritModel.AnalysisType { 62 case pb.AnalysisType_COMPILE_FAILURE_ANALYSIS: 63 message, err = compileFailureComment(ctx, culpritModel, "", "supportComment") 64 if err != nil { 65 return errors.Annotate(err, "generate compile failure support comment").Err() 66 } 67 case pb.AnalysisType_TEST_FAILURE_ANALYSIS: 68 message, err = testFailureComment(ctx, culpritModel, "", "supportComment") 69 if err != nil { 70 return errors.Annotate(err, "generate test failure support comment").Err() 71 } 72 } 73 _, err = gerritClient.AddComment(ctx, revert, message) 74 if err != nil { 75 return errors.Annotate(err, 76 "error when adding supporting comment to existing revert").Err() 77 } 78 79 // Update tsmon metrics 80 err = updateCulpritActionCounter(ctx, culpritModel, ActionTypeCommentRevert) 81 if err != nil { 82 logging.Errorf(ctx, errors.Annotate(err, "updateCulpritActionCounter").Err().Error()) 83 } 84 85 // Update culprit for the supporting comment action 86 err = datastore.RunInTransaction(ctx, func(ctx context.Context) error { 87 e := datastore.Get(ctx, culpritModel) 88 if e != nil { 89 return e 90 } 91 92 // set the flag to record the revert has a supporting comment from LUCI Bisection 93 culpritModel.HasSupportRevertComment = true 94 culpritModel.SupportRevertCommentTime = clock.Now(ctx) 95 96 return datastore.Put(ctx, culpritModel) 97 }, nil) 98 99 if err != nil { 100 return errors.Annotate(err, 101 "couldn't update suspect details when commenting support for existing revert").Err() 102 } 103 return nil 104 } 105 106 // commentReasonOnCulprit adds a comment from LUCI Bisection on a culprit CL 107 // explaining why a revert was not automatically created 108 func commentReasonOnCulprit(ctx context.Context, gerritClient *gerrit.Client, 109 culpritModel *model.Suspect, culprit *gerritpb.ChangeInfo, reason string) error { 110 logging.Debugf(ctx, "commenting on culprit %s~%d; a revert could not be"+ 111 " created because %s", culprit.Project, culprit.Number, reason) 112 113 lbCommented, err := gerrit.HasLUCIBisectionComment(ctx, culprit) 114 if err != nil { 115 return errors.Annotate(err, 116 "failed handling failed revert creation when checking for pre-existing comment").Err() 117 } 118 119 if lbCommented { 120 // Culprit already has a comment by LUCI Bisection - no further action 121 // required 122 saveInactionReason(ctx, culpritModel, pb.CulpritInactionReason_CULPRIT_HAS_COMMENT) 123 return nil 124 } 125 126 var message string 127 switch culpritModel.AnalysisType { 128 case pb.AnalysisType_COMPILE_FAILURE_ANALYSIS: 129 message, err = compileFailureComment(ctx, culpritModel, reason, "blameComment") 130 if err != nil { 131 return errors.Annotate(err, "generate compile failure blame comment").Err() 132 } 133 case pb.AnalysisType_TEST_FAILURE_ANALYSIS: 134 message, err = testFailureComment(ctx, culpritModel, reason, "blameComment") 135 if err != nil { 136 return errors.Annotate(err, "generate test failure blame comment").Err() 137 } 138 } 139 140 _, err = gerritClient.AddComment(ctx, culprit, message) 141 if err != nil { 142 return errors.Annotate(err, "error when commenting on culprit").Err() 143 } 144 145 // Update tsmon metrics 146 err = updateCulpritActionCounter(ctx, culpritModel, ActionTypeCommentCulprit) 147 if err != nil { 148 logging.Errorf(ctx, errors.Annotate(err, "updateCulpritActionCounter").Err().Error()) 149 } 150 151 // Update culprit for the comment action 152 err = datastore.RunInTransaction(ctx, func(ctx context.Context) error { 153 e := datastore.Get(ctx, culpritModel) 154 if e != nil { 155 return e 156 } 157 158 // set the flag to note that the culprit has a comment from LUCI Bisection 159 culpritModel.HasCulpritComment = true 160 culpritModel.CulpritCommentTime = clock.Now(ctx) 161 162 return datastore.Put(ctx, culpritModel) 163 }, nil) 164 165 if err != nil { 166 return errors.Annotate(err, 167 "couldn't update suspect details when commenting on the culprit").Err() 168 } 169 return nil 170 } 171 172 // sendRevertForReview adds a comment from LUCI Bisection on a revert CL 173 // explaining why a revert was not automatically submitted. 174 func sendRevertForReview(ctx context.Context, gerritClient *gerrit.Client, 175 culpritModel *model.Suspect, revert *gerritpb.ChangeInfo, reason string) error { 176 logging.Debugf(ctx, "sending revert %s~%d for review because %s", 177 revert.Project, revert.Number, reason) 178 179 // Get on-call arborists 180 reviewerEmails, err := rotationproxy.GetOnCallEmails(ctx, 181 culpritModel.GitilesCommit.Project) 182 if err != nil { 183 return errors.Annotate(err, "failed getting reviewers for manual review").Err() 184 } 185 186 // For now, no accounts are additionally CC'd 187 ccEmails := []string{} 188 189 message := fmt.Sprintf("LUCI Bisection could not automatically"+ 190 " submit this revert because %s.", reason) 191 _, err = gerritClient.SendForReview(ctx, revert, message, 192 reviewerEmails, ccEmails) 193 return err 194 }