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  }