mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-27 05:46:13 +01:00
274 lines
10 KiB
Go
Vendored
274 lines
10 KiB
Go
Vendored
// Copyright 2018 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package interpreter provides functions to evaluate parsed expressions with
|
|
// the option to augment the evaluation with inputs and functions supplied at
|
|
// evaluation time.
|
|
package interpreter
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/google/cel-go/common/ast"
|
|
"github.com/google/cel-go/common/containers"
|
|
"github.com/google/cel-go/common/types"
|
|
"github.com/google/cel-go/common/types/ref"
|
|
)
|
|
|
|
// PlannerOption configures the program plan options during interpretable setup.
|
|
type PlannerOption func(*planner) (*planner, error)
|
|
|
|
// Interpreter generates a new Interpretable from a checked or unchecked expression.
|
|
type Interpreter interface {
|
|
// NewInterpretable creates an Interpretable from a checked expression and an
|
|
// optional list of PlannerOption values.
|
|
NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (Interpretable, error)
|
|
}
|
|
|
|
// EvalObserver is a functional interface that accepts an expression id and an observed value.
|
|
// The id identifies the expression that was evaluated, the programStep is the Interpretable or Qualifier that
|
|
// was evaluated and value is the result of the evaluation.
|
|
type EvalObserver func(vars Activation, id int64, programStep any, value ref.Val)
|
|
|
|
// StatefulObserver observes evaluation while tracking or utilizing stateful behavior.
|
|
type StatefulObserver interface {
|
|
// InitState configures stateful metadata on the activation.
|
|
InitState(Activation) (Activation, error)
|
|
|
|
// GetState retrieves the stateful metadata from the activation.
|
|
GetState(Activation) any
|
|
|
|
// Observe passes the activation and relevant evaluation metadata to the observer.
|
|
// The observe method is expected to do the equivalent of GetState(vars) in order
|
|
// to find the metadata that needs to be updated upon invocation.
|
|
Observe(vars Activation, id int64, programStep any, value ref.Val)
|
|
}
|
|
|
|
// EvalCancelledError represents a cancelled program evaluation operation.
|
|
type EvalCancelledError struct {
|
|
Message string
|
|
// Type identifies the cause of the cancellation.
|
|
Cause CancellationCause
|
|
}
|
|
|
|
func (e EvalCancelledError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
// CancellationCause enumerates the ways a program evaluation operation can be cancelled.
|
|
type CancellationCause int
|
|
|
|
const (
|
|
// ContextCancelled indicates that the operation was cancelled in response to a Golang context cancellation.
|
|
ContextCancelled CancellationCause = iota
|
|
|
|
// CostLimitExceeded indicates that the operation was cancelled in response to the actual cost limit being
|
|
// exceeded.
|
|
CostLimitExceeded
|
|
)
|
|
|
|
// evalStateOption configures the evalStateFactory behavior.
|
|
type evalStateOption func(*evalStateFactory) *evalStateFactory
|
|
|
|
// EvalStateFactory configures the EvalState generator to be used by the EvalStateObserver.
|
|
func EvalStateFactory(factory func() EvalState) evalStateOption {
|
|
return func(fac *evalStateFactory) *evalStateFactory {
|
|
fac.factory = factory
|
|
return fac
|
|
}
|
|
}
|
|
|
|
// EvalStateObserver provides an observer which records the value associated with the given expression id.
|
|
// EvalState must be provided to the observer.
|
|
func EvalStateObserver(opts ...evalStateOption) PlannerOption {
|
|
et := &evalStateFactory{factory: NewEvalState}
|
|
for _, o := range opts {
|
|
et = o(et)
|
|
}
|
|
return func(p *planner) (*planner, error) {
|
|
if et.factory == nil {
|
|
return nil, errors.New("eval state factory not configured")
|
|
}
|
|
p.observers = append(p.observers, et)
|
|
p.decorators = append(p.decorators, decObserveEval(et.Observe))
|
|
return p, nil
|
|
}
|
|
}
|
|
|
|
// evalStateConverter identifies an object which is convertible to an EvalState instance.
|
|
type evalStateConverter interface {
|
|
asEvalState() EvalState
|
|
}
|
|
|
|
// evalStateActivation hides state in the Activation in a manner not accessible to expressions.
|
|
type evalStateActivation struct {
|
|
vars Activation
|
|
state EvalState
|
|
}
|
|
|
|
// ResolveName proxies variable lookups to the backing activation.
|
|
func (esa evalStateActivation) ResolveName(name string) (any, bool) {
|
|
return esa.vars.ResolveName(name)
|
|
}
|
|
|
|
// Parent proxies parent lookups to the backing activation.
|
|
func (esa evalStateActivation) Parent() Activation {
|
|
return esa.vars
|
|
}
|
|
|
|
// AsPartialActivation supports conversion to a partial activation in order to detect unknown attributes.
|
|
func (esa evalStateActivation) AsPartialActivation() (PartialActivation, bool) {
|
|
return AsPartialActivation(esa.vars)
|
|
}
|
|
|
|
// asEvalState implements the evalStateConverter method.
|
|
func (esa evalStateActivation) asEvalState() EvalState {
|
|
return esa.state
|
|
}
|
|
|
|
// asEvalState walks the Activation hierarchy and returns the first EvalState found, if present.
|
|
func asEvalState(vars Activation) (EvalState, bool) {
|
|
if conv, ok := vars.(evalStateConverter); ok {
|
|
return conv.asEvalState(), true
|
|
}
|
|
if vars.Parent() != nil {
|
|
return asEvalState(vars.Parent())
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// evalStateFactory holds a reference to a factory function that produces an EvalState instance.
|
|
type evalStateFactory struct {
|
|
factory func() EvalState
|
|
}
|
|
|
|
// InitState produces an EvalState instance and bundles it into the Activation in a way which is
|
|
// not visible to expression evaluation.
|
|
func (et *evalStateFactory) InitState(vars Activation) (Activation, error) {
|
|
state := et.factory()
|
|
return evalStateActivation{vars: vars, state: state}, nil
|
|
}
|
|
|
|
// GetState extracts the EvalState from the Activation.
|
|
func (et *evalStateFactory) GetState(vars Activation) any {
|
|
if state, found := asEvalState(vars); found {
|
|
return state
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Observe records the evaluation state for a given expression node and program step.
|
|
func (et *evalStateFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) {
|
|
state, found := asEvalState(vars)
|
|
if !found {
|
|
return
|
|
}
|
|
state.SetValue(id, val)
|
|
}
|
|
|
|
// CustomDecorator configures a custom interpretable decorator for the program.
|
|
func CustomDecorator(dec InterpretableDecorator) PlannerOption {
|
|
return func(p *planner) (*planner, error) {
|
|
p.decorators = append(p.decorators, dec)
|
|
return p, nil
|
|
}
|
|
}
|
|
|
|
// ExhaustiveEval replaces operations that short-circuit with versions that evaluate
|
|
// expressions and couples this behavior with the TrackState() decorator to provide
|
|
// insight into the evaluation state of the entire expression. EvalState must be
|
|
// provided to the decorator. This decorator is not thread-safe, and the EvalState
|
|
// must be reset between Eval() calls.
|
|
func ExhaustiveEval() PlannerOption {
|
|
return CustomDecorator(decDisableShortcircuits())
|
|
}
|
|
|
|
// InterruptableEval annotates comprehension loops with information that indicates they
|
|
// should check the `#interrupted` state within a custom Activation.
|
|
//
|
|
// The custom activation is currently managed higher up in the stack within the 'cel' package
|
|
// and should not require any custom support on behalf of callers.
|
|
func InterruptableEval() PlannerOption {
|
|
return CustomDecorator(decInterruptFolds())
|
|
}
|
|
|
|
// Optimize will pre-compute operations such as list and map construction and optimize
|
|
// call arguments to set membership tests. The set of optimizations will increase over time.
|
|
func Optimize() PlannerOption {
|
|
return CustomDecorator(decOptimize())
|
|
}
|
|
|
|
// RegexOptimization provides a way to replace an InterpretableCall for a regex function when the
|
|
// RegexIndex argument is a string constant. Typically, the Factory would compile the regex pattern at
|
|
// RegexIndex and report any errors (at program creation time) and then use the compiled regex for
|
|
// all regex function invocations.
|
|
type RegexOptimization struct {
|
|
// Function is the name of the function to optimize.
|
|
Function string
|
|
// OverloadID is the ID of the overload to optimize.
|
|
OverloadID string
|
|
// RegexIndex is the index position of the regex pattern argument. Only calls to the function where this argument is
|
|
// a string constant will be delegated to this optimizer.
|
|
RegexIndex int
|
|
// Factory constructs a replacement InterpretableCall node that optimizes the regex function call. Factory is
|
|
// provided with the unoptimized regex call and the string constant at the RegexIndex argument.
|
|
// The Factory may compile the regex for use across all invocations of the call, return any errors and
|
|
// return an interpreter.NewCall with the desired regex optimized function impl.
|
|
Factory func(call InterpretableCall, regexPattern string) (InterpretableCall, error)
|
|
}
|
|
|
|
// CompileRegexConstants compiles regex pattern string constants at program creation time and reports any regex pattern
|
|
// compile errors.
|
|
func CompileRegexConstants(regexOptimizations ...*RegexOptimization) PlannerOption {
|
|
return CustomDecorator(decRegexOptimizer(regexOptimizations...))
|
|
}
|
|
|
|
type exprInterpreter struct {
|
|
dispatcher Dispatcher
|
|
container *containers.Container
|
|
provider types.Provider
|
|
adapter types.Adapter
|
|
attrFactory AttributeFactory
|
|
}
|
|
|
|
// NewInterpreter builds an Interpreter from a Dispatcher and TypeProvider which will be used
|
|
// throughout the Eval of all Interpretable instances generated from it.
|
|
func NewInterpreter(dispatcher Dispatcher,
|
|
container *containers.Container,
|
|
provider types.Provider,
|
|
adapter types.Adapter,
|
|
attrFactory AttributeFactory) Interpreter {
|
|
return &exprInterpreter{
|
|
dispatcher: dispatcher,
|
|
container: container,
|
|
provider: provider,
|
|
adapter: adapter,
|
|
attrFactory: attrFactory}
|
|
}
|
|
|
|
// NewIntepretable implements the Interpreter interface method.
|
|
func (i *exprInterpreter) NewInterpretable(
|
|
checked *ast.AST,
|
|
opts ...PlannerOption) (Interpretable, error) {
|
|
p := newPlanner(i.dispatcher, i.provider, i.adapter, i.attrFactory, i.container, checked)
|
|
var err error
|
|
for _, o := range opts {
|
|
p, err = o(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return p.Plan(checked.Expr())
|
|
}
|