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  }