go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/scheduler/appengine/engine/policy/policy.go (about) 1 // Copyright 2018 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 policy contains implementation of triggering policy functions. 16 package policy 17 18 import ( 19 "fmt" 20 "time" 21 22 "google.golang.org/protobuf/proto" 23 "google.golang.org/protobuf/types/known/durationpb" 24 25 "go.chromium.org/luci/common/errors" 26 27 "go.chromium.org/luci/scheduler/appengine/internal" 28 "go.chromium.org/luci/scheduler/appengine/messages" 29 "go.chromium.org/luci/scheduler/appengine/task" 30 ) 31 32 // Environment is used by the triggering policy for getting transient 33 // information about the environment and for logging. 34 // 35 // TODO(vadimsh): This is intentionally mostly empty for now. Will be extended 36 // on as-needed basis. 37 type Environment interface { 38 // DebugLog appends a line to the triage text log. 39 DebugLog(format string, args ...any) 40 } 41 42 // In contains parameters for a triggering policy function. 43 type In struct { 44 // Now is the time when the policy function was called. 45 Now time.Time 46 // ActiveInvocations is a set of currently running invocations of the job. 47 ActiveInvocations []int64 48 // Triggers is a list of pending triggers sorted by time, more recent last. 49 Triggers []*internal.Trigger 50 } 51 52 // Out contains the decision of a triggering policy function. 53 type Out struct { 54 // Requests is a list of requests to start new invocations (if any). 55 // 56 // Each request contains parameters that will be passed to a new invocation by 57 // the engine. The policy is responsible for filling them in. 58 // 59 // Triggers specified in the each request will be removed from the set of 60 // pending triggers (they are consumed). 61 Requests []task.Request 62 63 // Discard is a list of triggers that the policy decided aren't needed anymore 64 // and should be discarded by the engine. 65 // 66 // A trigger associated with a request must not be also slated for discard. 67 Discard []*internal.Trigger 68 } 69 70 // Func is the concrete implementation of a triggering policy. 71 // 72 // It looks at the current state of the job and its pending triggers list and 73 // (optionally) emits a bunch of requests to start new invocations. 74 // 75 // It is a pure function without any side effects (except, perhaps, logging into 76 // the given environment). 77 type Func func(Environment, In) Out 78 79 // New is a factory that takes TriggeringPolicy proto and returns a concrete 80 // function that implements this policy. 81 // 82 // The returned function will be used only during one triage round and then 83 // discarded. 84 // 85 // The caller is responsible for filling in all default values in the proto if 86 // necessary. Use UnmarshalDefinition to deserialize TriggeringPolicy filling in 87 // the defaults. 88 // 89 // Returns an error if the TriggeringPolicy message can't be 90 // understood (for example, it references an undefined policy kind). 91 func New(p *messages.TriggeringPolicy) (Func, error) { 92 // There will be more kinds here, so this should technically be a switch, even 93 // though currently it is not a very interesting switch. 94 switch p.Kind { 95 case messages.TriggeringPolicy_GREEDY_BATCHING: 96 return GreedyBatchingPolicy(int(p.MaxConcurrentInvocations), int(p.MaxBatchSize)) 97 case messages.TriggeringPolicy_LOGARITHMIC_BATCHING: 98 return LogarithmicBatchingPolicy(int(p.MaxConcurrentInvocations), int(p.MaxBatchSize), float64(p.LogBase)) 99 case messages.TriggeringPolicy_NEWEST_FIRST: 100 return NewestFirstPolicy(int(p.MaxConcurrentInvocations), p.PendingTimeout.AsDuration()) 101 default: 102 return nil, errors.Reason("unrecognized triggering policy kind %d", p.Kind).Err() 103 } 104 } 105 106 var defaultPolicy = messages.TriggeringPolicy{ 107 Kind: messages.TriggeringPolicy_GREEDY_BATCHING, 108 MaxConcurrentInvocations: 1, 109 MaxBatchSize: 1000, 110 PendingTimeout: durationpb.New(defaultPendingTimeout), 111 } 112 113 const defaultPendingTimeout = 7 * 24 * time.Hour // 7 days. 114 115 // Default instantiates default triggering policy function. 116 // 117 // Is is used if jobs do not define a triggering policy or it can't be 118 // understood. 119 func Default() Func { 120 f, err := New(&defaultPolicy) 121 if err != nil { 122 panic(fmt.Errorf("failed to instantiate default triggering policy - %s", err)) 123 } 124 return f 125 } 126 127 // UnmarshalDefinition deserializes TriggeringPolicy, filling in defaults. 128 func UnmarshalDefinition(b []byte) (*messages.TriggeringPolicy, error) { 129 msg := &messages.TriggeringPolicy{} 130 if len(b) != 0 { 131 if err := proto.Unmarshal(b, msg); err != nil { 132 return nil, errors.Annotate(err, "failed to unmarshal TriggeringPolicy").Err() 133 } 134 } 135 if msg.Kind == 0 { 136 msg.Kind = defaultPolicy.Kind 137 } 138 if msg.MaxConcurrentInvocations == 0 { 139 msg.MaxConcurrentInvocations = defaultPolicy.MaxConcurrentInvocations 140 } 141 if msg.MaxBatchSize == 0 { 142 msg.MaxBatchSize = defaultPolicy.MaxBatchSize 143 } 144 if msg.Kind == messages.TriggeringPolicy_NEWEST_FIRST && msg.PendingTimeout.AsDuration() == 0 { 145 msg.PendingTimeout = defaultPolicy.PendingTimeout 146 } 147 return msg, nil 148 }