Skip to content

Commit

Permalink
fix: truncate label values longer than 63 characters
Browse files Browse the repository at this point in the history
Signed-off-by: Omer Aplatony <[email protected]>
  • Loading branch information
omerap12 committed Nov 30, 2024
1 parent 2adbc81 commit f900598
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 19 deletions.
22 changes: 8 additions & 14 deletions controllers/keda/hpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"fmt"
"sort"
"strconv"
"strings"
"unicode"

"github.com/go-logr/logr"
autoscalingv2 "k8s.io/api/autoscaling/v2"
Expand Down Expand Up @@ -82,21 +80,16 @@ func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logg
}

// label can have max 63 chars
labelName := getHPAName(scaledObject)
if len(labelName) > 63 {
labelName = labelName[:63]
labelName = strings.TrimRightFunc(labelName, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
})
}
labelName := kedacontrollerutil.Truncate(getHPAName(scaledObject), 63)
scaleObjectName := kedacontrollerutil.Truncate(scaledObject.Name, 63)
labels := map[string]string{
"app.kubernetes.io/name": labelName,
"app.kubernetes.io/version": version.Version,
"app.kubernetes.io/part-of": scaledObject.Name,
"app.kubernetes.io/part-of": scaleObjectName,
"app.kubernetes.io/managed-by": "keda-operator",
}
for key, value := range scaledObject.ObjectMeta.Labels {
labels[key] = value
labels[key] = kedacontrollerutil.Truncate(value, 63)
}

minReplicas := scaledObject.GetHPAMinReplicas()
Expand Down Expand Up @@ -221,6 +214,7 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context,
}

metricSpecs := cache.GetMetricSpecForScaling(ctx)
scaledObjectName := kedacontrollerutil.Truncate(scaledObject.Name, 63)

for _, metricSpec := range metricSpecs {
if metricSpec.Resource != nil {
Expand All @@ -230,12 +224,12 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context,
if metricSpec.External != nil {
externalMetricName := metricSpec.External.Metric.Name
if kedacontrollerutil.Contains(externalMetricNames, externalMetricName) {
return nil, fmt.Errorf("metricName %s defined multiple times in ScaledObject %s", externalMetricName, scaledObject.Name)
return nil, fmt.Errorf("metricName %s defined multiple times in ScaledObject %s", externalMetricName, scaledObjectName)
}

// add the scaledobject.keda.sh/name label. This is how the MetricsAdapter will know which scaledobject a metric is for when the HPA queries it.
metricSpec.External.Metric.Selector = &metav1.LabelSelector{MatchLabels: make(map[string]string)}
metricSpec.External.Metric.Selector.MatchLabels[kedav1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name
metricSpec.External.Metric.Selector.MatchLabels[kedav1alpha1.ScaledObjectOwnerAnnotation] = scaledObjectName
externalMetricNames = append(externalMetricNames, externalMetricName)
}
}
Expand Down Expand Up @@ -293,7 +287,7 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context,
Metric: autoscalingv2.MetricIdentifier{
Name: compMetricName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{kedav1alpha1.ScaledObjectOwnerAnnotation: scaledObject.Name},
MatchLabels: map[string]string{kedav1alpha1.ScaledObjectOwnerAnnotation: scaledObjectName},
},
},
Target: correctHpaTarget,
Expand Down
11 changes: 6 additions & 5 deletions controllers/keda/scaledobject_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request
conditions.SetFallbackCondition(metav1.ConditionFalse, "NoFallbackFound", "No fallbacks are active on this scaled object")
}

metricscollector.RecordScaledObjectPaused(scaledObject.Namespace, scaledObject.Name, conditions.GetPausedCondition().Status == metav1.ConditionTrue)
metricscollector.RecordScaledObjectPaused(scaledObject.Namespace, kedacontrollerutil.Truncate(scaledObject.Name, 63), conditions.GetPausedCondition().Status == metav1.ConditionTrue)

if err := kedastatus.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, &conditions); err != nil {
r.EventEmitter.Emit(scaledObject, req.NamespacedName.Namespace, corev1.EventTypeWarning, eventingv1alpha1.ScaledObjectFailedType, eventreason.ScaledObjectUpdateFailed, err.Error())
Expand Down Expand Up @@ -319,17 +319,18 @@ func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logg
// ensureScaledObjectLabel ensures that scaledobject.keda.sh/name=<scaledObject.Name> label exist in the ScaledObject
// This is how the MetricsAdapter will know which ScaledObject a metric is for when the HPA queries it.
func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error {
scaledObjectNameTruncated := kedacontrollerutil.Truncate(scaledObject.Name, 63)
if scaledObject.Labels == nil {
scaledObject.Labels = map[string]string{kedav1alpha1.ScaledObjectOwnerAnnotation: scaledObject.Name}
scaledObject.Labels = map[string]string{kedav1alpha1.ScaledObjectOwnerAnnotation: scaledObjectNameTruncated}
} else {
value, found := scaledObject.Labels[kedav1alpha1.ScaledObjectOwnerAnnotation]
if found && value == scaledObject.Name {
if found && value == scaledObjectNameTruncated {
return nil
}
scaledObject.Labels[kedav1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name
scaledObject.Labels[kedav1alpha1.ScaledObjectOwnerAnnotation] = scaledObjectNameTruncated
}

logger.V(1).Info("Adding \"scaledobject.keda.sh/name\" label on ScaledObject", "value", scaledObject.Name)
logger.V(1).Info("Adding \"scaledobject.keda.sh/name\" label on ScaledObject", "value", scaledObjectNameTruncated)
return r.Client.Update(ctx, scaledObject)
}

Expand Down
16 changes: 16 additions & 0 deletions controllers/keda/util/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package util

import (
"strings"
"unicode"
)

func Truncate(s string, threshold int) string {
if len(s) > threshold {
s = s[:threshold]
s = strings.TrimRightFunc(s, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
})
}
return s
}
42 changes: 42 additions & 0 deletions controllers/keda/util/string_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package util

import (
"testing"
)

func TestTruncate(t *testing.T) {
tests := []struct {
name string
input string
threshold int
want string
}{
{
name: "string shorter than threshold",
input: "hello",
threshold: 10,
want: "hello",
},
{
name: "string longer than threshold ending with special chars",
input: "abc---def---ghi---",
threshold: 5,
want: "abc",
},
{
name: "63 character limit case",
input: "this-is-64-characters-name-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
threshold: 63,
want: "this-is-64-characters-name-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"[:63],
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Truncate(tt.input, tt.threshold)
if got != tt.want {
t.Errorf("Truncuate() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit f900598

Please sign in to comment.