go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/prjmanager/triager/purges.go (about) 1 // Copyright 2021 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 triager 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 "google.golang.org/protobuf/proto" 23 24 "go.chromium.org/luci/common/clock" 25 26 "go.chromium.org/luci/cv/internal/prjmanager/prjpb" 27 "go.chromium.org/luci/cv/internal/run" 28 ) 29 30 // stagePurges returns either purgeCLtasks for immediate purging OR the earliest 31 // time when a CL should be purged. Zero time means no purges to be done. 32 func stagePurges(ctx context.Context, cls map[int64]*clInfo, pm pmState) ([]*prjpb.PurgeCLTask, time.Time) { 33 now := clock.Now(ctx) 34 var out []*prjpb.PurgeCLTask 35 next := time.Time{} 36 for clid, info := range cls { 37 if info.purgingCL != nil || info.pcl.GetOutdated() != nil { 38 // the CL is already being purged, do not schedule a new task. 39 continue 40 } 41 switch when := purgeETA(info, now, pm); { 42 case when.IsZero(): 43 case when.After(now): 44 next = earliest(next, when) 45 default: 46 purgingCl := &prjpb.PurgingCL{ 47 Clid: clid, 48 } 49 var triggers *run.Triggers 50 loop: 51 for _, pr := range info.purgeReasons { 52 switch v := pr.ApplyTo.(type) { 53 case *prjpb.PurgeReason_AllActiveTriggers: 54 purgingCl.ApplyTo = &prjpb.PurgingCL_AllActiveTriggers{AllActiveTriggers: true} 55 break loop 56 case *prjpb.PurgeReason_Triggers: 57 if triggers == nil { 58 triggers = &run.Triggers{} 59 purgingCl.ApplyTo = &prjpb.PurgingCL_Triggers{Triggers: triggers} 60 } 61 proto.Merge(triggers, v.Triggers) 62 } 63 } 64 out = append(out, &prjpb.PurgeCLTask{ 65 PurgeReasons: info.purgeReasons, 66 PurgingCl: purgingCl, 67 }) 68 } 69 } 70 return out, next 71 } 72 func (info *clInfo) isTriggered() bool { 73 return info.pcl.GetTriggers().GetCqVoteTrigger() != nil || info.pcl.GetTriggers().GetNewPatchsetRunTrigger() != nil 74 } 75 76 // purgeETA returns the earliest time a CL may be purged and Zero time if CL 77 // should not be purged at all. 78 func purgeETA(info *clInfo, now time.Time, pm pmState) time.Time { 79 if info.purgeReasons == nil { 80 return time.Time{} 81 } 82 if len(info.pcl.GetConfigGroupIndexes()) != 1 { 83 // In case of bad config, waiting doesn't help. 84 return now 85 } 86 if !info.isTriggered() { 87 panic(fmt.Errorf("CL %d is not triggered and thus can't be purged (reasons: %s)", info.pcl.GetClid(), info.purgeReasons)) 88 } 89 var eta time.Time 90 if info.pcl.GetTriggers().GetNewPatchsetRunTrigger() != nil { 91 eta = earliest(eta, now) 92 } 93 if info.pcl.GetTriggers().GetCqVoteTrigger() != nil { 94 cg := pm.ConfigGroup(info.pcl.GetConfigGroupIndexes()[0]) 95 d := cg.Content.GetCombineCls().GetStabilizationDelay() 96 if d == nil { 97 eta = earliest(eta, now) 98 } else { 99 100 t := info.lastCQVoteTriggered() 101 if t.IsZero() { 102 panic(fmt.Errorf("impossible: CQ label is triggered but triggering time is zero")) 103 } 104 eta = earliest(eta, t.Add(d.AsDuration())) 105 } 106 } 107 return eta 108 }