Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ed28b73
formula plus structure for external scaling
gauron99 May 16, 2023
d7676e6
combination works
gauron99 May 25, 2023
80f76b3
add externalScaling health check type, help function for client, redo…
gauron99 May 29, 2023
669de9b
to pull
gauron99 Jun 19, 2023
45e9d58
hack for recent cache error, added tests
gauron99 Jun 26, 2023
e06e250
fallback first iteration + conditions
gauron99 Jun 30, 2023
974e355
e2e fallback test, timeout, close connection on close cache
gauron99 Jul 4, 2023
bb7dd3b
semgrep
gauron99 Jul 4, 2023
e56f73b
add new manifest, comments
gauron99 Jul 4, 2023
3fa7b5f
nil checkers a refactors, new fallback unit tests
gauron99 Jul 7, 2023
66b6b55
remove prints, simplify, remove cyclical complexity of scale_handler,…
gauron99 Jul 11, 2023
6c56209
webhook tests, refresh scaler if not found during metric fetch, valid…
gauron99 Jul 12, 2023
459007c
hotfix
gauron99 Jul 12, 2023
d65a86b
new scale_handler test, add certificate for grpc clients, comment cle…
gauron99 Jul 17, 2023
5535846
add certDir to manifest
gauron99 Jul 17, 2023
c54899c
use existing fallback
gauron99 Jul 19, 2023
87f3837
keep resource metric in HPA and fix null pointer reference err invali…
gauron99 Jul 19, 2023
6db79a5
manifest change
gauron99 Jul 19, 2023
4849801
revert cacheObj as its not needed now
gauron99 Jul 19, 2023
4663f6a
formula only
gauron99 Jul 25, 2023
f1753b9
changelog
gauron99 Jul 25, 2023
1563510
skip cpu&mem triggers when checking for names
gauron99 Jul 25, 2023
3b5675b
webhook checks for trigger types
gauron99 Jul 26, 2023
74a6d6a
cleanup, remove non formula files
gauron99 Jul 26, 2023
374c6a2
some comments updates for renaming
gauron99 Sep 4, 2023
0f18df0
correct changelog order
gauron99 Sep 4, 2023
136fd88
new modifiers file, cache compiled formula
gauron99 Sep 18, 2023
934321b
changelog upd
gauron99 Sep 18, 2023
1864080
fix pointers in so webhook test
gauron99 Sep 18, 2023
da2c953
change structure name, add cache.recorder
gauron99 Sep 18, 2023
abdf036
update struct name, dummy compiler beforehand
gauron99 Sep 21, 2023
6bae7e3
webhook test, update expr version
gauron99 Sep 21, 2023
ba84b48
fix webhook test
gauron99 Sep 21, 2023
0f6a5b2
change test name
gauron99 Sep 21, 2023
bc2d033
test name change
gauron99 Sep 21, 2023
0ca278e
refactor of validator, big comments
gauron99 Sep 21, 2023
35ccadc
create SO method for checking SM, some feedback
gauron99 Sep 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
- **AWS SQS Scaler**: Support for scaling to include delayed messages. ([#4377](https://github.com/kedacore/keda/issues/4377))
- **Governance**: KEDA transitioned to CNCF Graduated project ([#63](https://github.com/kedacore/governance/issues/63))

#### Experimental

Here is an overview of all new **experimental** features:

- **General**: Add support for formula based evaluation of metric values ([#2440](https://github.com/kedacore/keda/issues/2440))

### Improvements

- **General**: Add more events for user checking ([#796](https://github.com/kedacore/keda/issues/3764))
Expand Down
20 changes: 20 additions & 0 deletions apis/keda/v1alpha1/scaledobject_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1

import (
"reflect"

autoscalingv2 "k8s.io/api/autoscaling/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -67,6 +69,9 @@ const (

// HealthStatusFailing means the status of the health object is failing
HealthStatusFailing HealthStatusType = "Failing"

// Composite metric name used for scalingModifiers composite metric
CompositeMetricName string = "composite-metric"
)

// ScaledObjectSpec is the spec for a ScaledObject resource
Expand Down Expand Up @@ -102,6 +107,14 @@ type AdvancedConfig struct {
HorizontalPodAutoscalerConfig *HorizontalPodAutoscalerConfig `json:"horizontalPodAutoscalerConfig,omitempty"`
// +optional
RestoreToOriginalReplicaCount bool `json:"restoreToOriginalReplicaCount,omitempty"`
// +optional
ScalingModifiers ScalingModifiers `json:"scalingModifiers,omitempty"`
}

// ScalingModifiers describes advanced scaling logic options like formula
type ScalingModifiers struct {
Formula string `json:"formula,omitempty"`
Target string `json:"target,omitempty"`
}

// HorizontalPodAutoscalerConfig specifies horizontal scale config
Expand Down Expand Up @@ -141,6 +154,8 @@ type ScaledObjectStatus struct {
// +optional
ResourceMetricNames []string `json:"resourceMetricNames,omitempty"`
// +optional
CompositeScalerName string `json:"compositeScalerName,omitempty"`
// +optional
Conditions Conditions `json:"conditions,omitempty"`
// +optional
Health map[string]HealthStatus `json:"health,omitempty"`
Expand All @@ -167,3 +182,8 @@ func init() {
func (so *ScaledObject) GenerateIdentifier() string {
return GenerateIdentifier("ScaledObject", so.Namespace, so.Name)
}

// IsUsingModifiers determines whether scalingModifiers are defined or not
func (so *ScaledObject) IsUsingModifiers() bool {
return so.Spec.Advanced != nil && !reflect.DeepEqual(so.Spec.Advanced.ScalingModifiers, ScalingModifiers{})
}
126 changes: 126 additions & 0 deletions apis/keda/v1alpha1/scaledobject_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ package v1alpha1
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"

"github.com/antonmedv/expr"
"github.com/antonmedv/expr/vm"
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -213,6 +217,16 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error {
}
}

// verify ScalingModifiers structure if defined in ScaledObject
if incomingSo.IsUsingModifiers() {
_, err = ValidateAndCompileScalingModifiers(incomingSo)
if err != nil {
scaledobjectlog.Error(err, "error validating ScalingModifiers")
prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "scaling-modifiers")

return err
}
}
return nil
}

Expand Down Expand Up @@ -297,3 +311,115 @@ func verifyCPUMemoryScalers(incomingSo *ScaledObject, action string) error {
}
return nil
}

// ValidateAndCompileScalingModifiers validates all combinations of given arguments
// and their values. Expects the whole structure's path to be defined (like .Advanced).
// As part of formula validation this function also compiles the formula
// (with dummy values that determine whether all necessary triggers are defined)
// and returns it to be stored in cache and reused.
func ValidateAndCompileScalingModifiers(so *ScaledObject) (*vm.Program, error) {
sm := so.Spec.Advanced.ScalingModifiers

if sm.Formula == "" {
return nil, fmt.Errorf("error ScalingModifiers.Formula is mandatory")
}

// validate formula if not empty
compiledFormula, err := validateScalingModifiersFormula(so)
if err != nil {
err := errors.Join(fmt.Errorf("error validating formula in ScalingModifiers"), err)
return nil, err
}
// validate target if not empty
err = validateScalingModifiersTarget(so)
if err != nil {
err := errors.Join(fmt.Errorf("error validating target in ScalingModifiers"), err)
return nil, err
}
return compiledFormula, nil
}

// validateScalingModifiersFormula helps validate the ScalingModifiers struct,
// specifically the formula.
func validateScalingModifiersFormula(so *ScaledObject) (*vm.Program, error) {
sm := so.Spec.Advanced.ScalingModifiers

// if formula is empty, nothing to validate
if sm.Formula == "" {
return nil, nil
}
// formula needs target because it's always transformed to composite-scaler
if sm.Target == "" {
return nil, fmt.Errorf("formula is given but target is empty")
}

// dummy value for compiled map of triggers
dummyValue := -1.0

// Compile & Run with dummy values to determine if all triggers in formula are
// defined (have names)
triggersMap := make(map[string]float64)
for _, trig := range so.Spec.Triggers {
// if resource metrics are given, skip
if trig.Type == cpuString || trig.Type == memoryString {
continue
}
if trig.Name != "" {
triggersMap[trig.Name] = dummyValue
}
}
compiled, err := expr.Compile(sm.Formula, expr.Env(triggersMap), expr.AsFloat64())
if err != nil {
return nil, err
}
_, err = expr.Run(compiled, triggersMap)
if err != nil {
return nil, err
}
return compiled, nil
}

func validateScalingModifiersTarget(so *ScaledObject) error {
sm := so.Spec.Advanced.ScalingModifiers

if sm.Target == "" {
return nil
}

// convert string to float
num, err := strconv.ParseFloat(sm.Target, 64)
if err != nil || num <= 0.0 {
return fmt.Errorf("error converting target for scalingModifiers (string->float) to valid target: %w", err)
}

// if target is given, composite-scaler will be passed to HPA -> all types
// need to be the same - make sure all metrics are of the same metricTargetType

var trigType autoscalingv2.MetricTargetType

// gauron99: possible TODO: more sofisticated check for trigger could be used here
// as well if solution is found (check just the right triggers that are used)
for _, trig := range so.Spec.Triggers {
if trig.Type == cpuString || trig.Type == memoryString || trig.Name == "" {
continue
}
var current autoscalingv2.MetricTargetType
if trig.MetricType == "" {
current = autoscalingv2.AverageValueMetricType // default is AverageValue
} else {
current = trig.MetricType
}
if trigType == "" {
trigType = current
} else if trigType != current {
err := fmt.Errorf("error trigger types are not the same for composite scaler: %s & %s", trigType, current)
return err
}
}
if trigType == autoscalingv2.UtilizationMetricType {
err := fmt.Errorf("error trigger type is Utilization, but it needs to be AverageValue or Value for external metrics")
return err
}

return nil
}
Loading