Skip to content

Commit 0e7662d

Browse files
authored
validate triggers also in the webhook (#4670)
Signed-off-by: Zbynek Roubalik <[email protected]>
1 parent 93851e7 commit 0e7662d

11 files changed

+238
-102
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
6868
### Fixes
6969

7070
- **Admission Webhooks**: Allow to remove the finalizer even if the ScaledObject isn't valid ([#4396](https://github.com/kedacore/keda/issue/4396))
71+
- **Admission Webhooks**: Check ScaledObjects with multiple triggers with non unique name ([#4664](https://github.com/kedacore/keda/issue/4664))
7172
- **ScaledJob**: Check if MaxReplicaCount is nil before access to it ([#4568]https://github.com/kedacore/keda/issues/4568)
7273
- **AWS SQS Scaler**: Respect `scaleOnInFlight` value ([#4276](https://github.com/kedacore/keda/issue/4276))
7374
- **Azure Pipelines**: Fix for disallowing `$top` on query when using `meta.parentID` method ([#4397])

apis/keda/v1alpha1/scaledobject_types.go

-24
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,6 @@ type ScaleTarget struct {
122122
EnvSourceContainerName string `json:"envSourceContainerName,omitempty"`
123123
}
124124

125-
// ScaleTriggers reference the scaler that will be used
126-
type ScaleTriggers struct {
127-
Type string `json:"type"`
128-
// +optional
129-
Name string `json:"name,omitempty"`
130-
131-
UseCachedMetrics bool `json:"useCachedMetrics,omitempty"`
132-
133-
Metadata map[string]string `json:"metadata"`
134-
// +optional
135-
AuthenticationRef *ScaledObjectAuthRef `json:"authenticationRef,omitempty"`
136-
// +optional
137-
MetricType autoscalingv2.MetricTargetType `json:"metricType,omitempty"`
138-
}
139-
140125
// +k8s:openapi-gen=true
141126

142127
// ScaledObjectStatus is the status for a ScaledObject resource
@@ -173,15 +158,6 @@ type ScaledObjectList struct {
173158
Items []ScaledObject `json:"items"`
174159
}
175160

176-
// ScaledObjectAuthRef points to the TriggerAuthentication or ClusterTriggerAuthentication object that
177-
// is used to authenticate the scaler with the environment
178-
type ScaledObjectAuthRef struct {
179-
Name string `json:"name"`
180-
// Kind of the resource being referred to. Defaults to TriggerAuthentication.
181-
// +optional
182-
Kind string `json:"kind,omitempty"`
183-
}
184-
185161
func init() {
186162
SchemeBuilder.Register(&ScaledObject{}, &ScaledObjectList{})
187163
}

apis/keda/v1alpha1/scaledobject_webhook.go

+21-10
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,34 @@ func isRemovingFinalizer(so *ScaledObject, old runtime.Object) bool {
9292

9393
func validateWorkload(so *ScaledObject, action string) (admission.Warnings, error) {
9494
prommetrics.RecordScaledObjectValidatingTotal(so.Namespace, action)
95-
err := verifyCPUMemoryScalers(so, action)
96-
if err != nil {
97-
return nil, err
98-
}
99-
err = verifyScaledObjects(so, action)
100-
if err != nil {
101-
return nil, err
95+
96+
verifyFunctions := []func(*ScaledObject, string) error{
97+
verifyCPUMemoryScalers,
98+
verifyTriggers,
99+
verifyScaledObjects,
100+
verifyHpas,
102101
}
103-
err = verifyHpas(so, action)
104-
if err != nil {
105-
return nil, err
102+
103+
for i := range verifyFunctions {
104+
err := verifyFunctions[i](so, action)
105+
if err != nil {
106+
return nil, err
107+
}
106108
}
107109

108110
scaledobjectlog.V(1).Info(fmt.Sprintf("scaledobject %s is valid", so.Name))
109111
return nil, nil
110112
}
111113

114+
func verifyTriggers(incomingSo *ScaledObject, action string) error {
115+
err := ValidateTriggers(scaledobjectlog.WithValues("name", incomingSo.Name), incomingSo.Spec.Triggers)
116+
if err != nil {
117+
scaledobjectlog.WithValues("name", incomingSo.Name).Error(err, "validation error")
118+
prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "incorrect-triggers")
119+
}
120+
return err
121+
}
122+
112123
func verifyHpas(incomingSo *ScaledObject, action string) error {
113124
hpaList := &autoscalingv2.HorizontalPodAutoscalerList{}
114125
opt := &client.ListOptions{
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright 2023 The KEDA Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/go-logr/logr"
23+
autoscalingv2 "k8s.io/api/autoscaling/v2"
24+
)
25+
26+
// ScaleTriggers reference the scaler that will be used
27+
type ScaleTriggers struct {
28+
Type string `json:"type"`
29+
// +optional
30+
Name string `json:"name,omitempty"`
31+
32+
UseCachedMetrics bool `json:"useCachedMetrics,omitempty"`
33+
34+
Metadata map[string]string `json:"metadata"`
35+
// +optional
36+
AuthenticationRef *AuthenticationRef `json:"authenticationRef,omitempty"`
37+
// +optional
38+
MetricType autoscalingv2.MetricTargetType `json:"metricType,omitempty"`
39+
}
40+
41+
// AuthenticationRef points to the TriggerAuthentication or ClusterTriggerAuthentication object that
42+
// is used to authenticate the scaler with the environment
43+
type AuthenticationRef struct {
44+
Name string `json:"name"`
45+
// Kind of the resource being referred to. Defaults to TriggerAuthentication.
46+
// +optional
47+
Kind string `json:"kind,omitempty"`
48+
}
49+
50+
// ValidateTriggers checks that general trigger metadata are valid, it checks:
51+
// - triggerNames in ScaledObject are unique
52+
// - useCachedMetrics is defined only for a supported triggers
53+
func ValidateTriggers(logger logr.Logger, triggers []ScaleTriggers) error {
54+
triggersCount := len(triggers)
55+
if triggers != nil && triggersCount > 0 {
56+
triggerNames := make(map[string]bool, triggersCount)
57+
for i := 0; i < triggersCount; i++ {
58+
trigger := triggers[i]
59+
60+
if trigger.UseCachedMetrics {
61+
if trigger.Type == "cpu" || trigger.Type == "memory" || trigger.Type == "cron" {
62+
return fmt.Errorf("property \"useCachedMetrics\" is not supported for %q scaler", trigger.Type)
63+
}
64+
}
65+
66+
// FIXME: DEPRECATED to be removed in v2.12
67+
_, hasMetricName := trigger.Metadata["metricName"]
68+
// aws-cloudwatch and huawei-cloudeye have a meaningful use of metricName
69+
if hasMetricName && trigger.Type != "aws-cloudwatch" && trigger.Type != "huawei-cloudeye" {
70+
logger.Info("\"metricName\" is deprecated and will be removed in v2.12, please do not set it anymore", "trigger.type", trigger.Type)
71+
}
72+
73+
name := trigger.Name
74+
if name != "" {
75+
if _, found := triggerNames[name]; found {
76+
// found duplicate name
77+
return fmt.Errorf("triggerName %q is defined multiple times in the ScaledObject, but it must be unique", name)
78+
}
79+
triggerNames[name] = true
80+
}
81+
}
82+
}
83+
84+
return nil
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package v1alpha1
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-logr/logr"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestValidateTriggers(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
triggers []ScaleTriggers
14+
expectedErrMsg string
15+
}{
16+
{
17+
name: "valid triggers",
18+
triggers: []ScaleTriggers{
19+
{
20+
Name: "trigger1",
21+
Type: "cpu",
22+
},
23+
{
24+
Name: "trigger2",
25+
Type: "prometheus",
26+
},
27+
},
28+
expectedErrMsg: "",
29+
},
30+
{
31+
name: "duplicate trigger names",
32+
triggers: []ScaleTriggers{
33+
{
34+
Name: "trigger1",
35+
Type: "cpu",
36+
},
37+
{
38+
Name: "trigger1",
39+
Type: "prometheus",
40+
},
41+
},
42+
expectedErrMsg: "triggerName \"trigger1\" is defined multiple times in the ScaledObject, but it must be unique",
43+
},
44+
{
45+
name: "unsupported useCachedMetrics property for cpu scaler",
46+
triggers: []ScaleTriggers{
47+
{
48+
Name: "trigger1",
49+
Type: "cpu",
50+
UseCachedMetrics: true,
51+
},
52+
},
53+
expectedErrMsg: "property \"useCachedMetrics\" is not supported for \"cpu\" scaler",
54+
},
55+
{
56+
name: "unsupported useCachedMetrics property for memory scaler",
57+
triggers: []ScaleTriggers{
58+
{
59+
Name: "trigger2",
60+
Type: "memory",
61+
UseCachedMetrics: true,
62+
},
63+
},
64+
expectedErrMsg: "property \"useCachedMetrics\" is not supported for \"memory\" scaler",
65+
},
66+
{
67+
name: "unsupported useCachedMetrics property for cron scaler",
68+
triggers: []ScaleTriggers{
69+
{
70+
Name: "trigger3",
71+
Type: "cron",
72+
UseCachedMetrics: true,
73+
},
74+
},
75+
expectedErrMsg: "property \"useCachedMetrics\" is not supported for \"cron\" scaler",
76+
},
77+
{
78+
name: "supported useCachedMetrics property for kafka scaler",
79+
triggers: []ScaleTriggers{
80+
{
81+
Name: "trigger4",
82+
Type: "kafka",
83+
UseCachedMetrics: true,
84+
},
85+
},
86+
expectedErrMsg: "",
87+
},
88+
}
89+
90+
for _, test := range tests {
91+
tt := test
92+
t.Run(test.name, func(t *testing.T) {
93+
err := ValidateTriggers(logr.Discard(), tt.triggers)
94+
if test.expectedErrMsg == "" {
95+
assert.NoError(t, err)
96+
} else {
97+
assert.EqualError(t, err, tt.expectedErrMsg)
98+
}
99+
})
100+
}
101+
}

apis/keda/v1alpha1/zz_generated.deepcopy.go

+16-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/keda.sh_scaledjobs.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8224,7 +8224,7 @@ spec:
82248224
description: ScaleTriggers reference the scaler that will be used
82258225
properties:
82268226
authenticationRef:
8227-
description: ScaledObjectAuthRef points to the TriggerAuthentication
8227+
description: AuthenticationRef points to the TriggerAuthentication
82288228
or ClusterTriggerAuthentication object that is used to authenticate
82298229
the scaler with the environment
82308230
properties:

config/crd/bases/keda.sh_scaledobjects.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ spec:
252252
description: ScaleTriggers reference the scaler that will be used
253253
properties:
254254
authenticationRef:
255-
description: ScaledObjectAuthRef points to the TriggerAuthentication
255+
description: AuthenticationRef points to the TriggerAuthentication
256256
or ClusterTriggerAuthentication object that is used to authenticate
257257
the scaler with the environment
258258
properties:

0 commit comments

Comments
 (0)