forked from google/knative-gcp
/
annotations.go
163 lines (141 loc) · 7.77 KB
/
annotations.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
Copyright 2020 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 v1alpha1
import (
"context"
"fmt"
"math"
"strconv"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"
)
const (
// Autoscaling refers to the autoscaling group.
Autoscaling = "autoscaling.knative.dev"
// AutoscalingClassAnnotation is the annotation for the explicit class of
// scaler that a particular resource has opted into.
AutoscalingClassAnnotation = Autoscaling + "/class"
// AutoscalingMinScaleAnnotation is the annotation to specify the minimum number of pods to scale to.
AutoscalingMinScaleAnnotation = Autoscaling + "/minScale"
// AutoscalingMaxScaleAnnotation is the annotation to specify the maximum number of pods to scale to.
AutoscalingMaxScaleAnnotation = Autoscaling + "/maxScale"
// KEDA is Keda autoscaler.
KEDA = "keda.autoscaling.knative.dev"
// KedaAutoscalingPollingIntervalAnnotation is the annotation that refers to the interval in seconds Keda
// uses to poll metrics in order to inform its scaling decisions.
KedaAutoscalingPollingIntervalAnnotation = KEDA + "/pollingInterval"
// KedaAutoscalingCooldownPeriodAnnotation is the annotation that refers to the period Keda waits until it
// scales a Deployment down.
KedaAutoscalingCooldownPeriodAnnotation = KEDA + "/cooldownPeriod"
// KedaAutoscalingSubscriptionSizeAnnotation is the annotation that refers to the size of unacked messages in a
// Pub/Sub subscription that Keda uses in order to decide when and by how much to scale out.
KedaAutoscalingSubscriptionSizeAnnotation = KEDA + "/subscriptionSize"
// defaultMinScale is the default minimum set of Pods the scaler should
// downscale the resource to.
defaultMinScale = "0"
// defaultMaxScale is the default maximum set of Pods the scaler should
// upscale the resource to.
defaultMaxScale = "1"
// defaultKedaPollingInterval is the default polling interval in seconds Keda uses to poll metrics.
defaultKedaPollingInterval = "15"
// defaultKedaCooldownPeriod is the default cooldown period in seconds Keda uses to downscale resources.
defaultKedaCooldownPeriod = "120"
// defaultKedaSubscriptionSize is the default number of unacked messages in a Pub/Sub subscription that Keda
// uses to scale out resources.
defaultKedaSubscriptionSize = "100"
// minimumMinScale is the minimum allowed value for the AutoscalingMinScaleAnnotation annotation.
minimumMinScale = 0
// minimumMaxScale is the minimum allowed value for the AutoscalingMaxScaleAnnotation annotation.
minimumMaxScale = 1
// minimumKedaPollingInterval is the minimum allowed value for the KedaAutoscalingPollingIntervalAnnotation annotation.
minimumKedaPollingInterval = 5
// minimumKedaCooldownPeriod is the minimum allowed value for the KedaAutoscalingCooldownPeriodAnnotation annotation.
minimumKedaCooldownPeriod = 15
// minimumKedaSubscriptionSize is the minimum allowed value for the KedaAutoscalingSubscriptionSizeAnnotation annotation.
minimumKedaSubscriptionSize = 5
)
func SetAutoscalingAnnotationsDefaults(ctx context.Context, obj *metav1.ObjectMeta) {
// If autoscaling was configured, then set defaults.
if _, ok := obj.Annotations[AutoscalingClassAnnotation]; ok {
setDefaultAnnotationIfNotPresent(obj, AutoscalingMinScaleAnnotation, defaultMinScale)
setDefaultAnnotationIfNotPresent(obj, AutoscalingMaxScaleAnnotation, defaultMaxScale)
setDefaultAnnotationIfNotPresent(obj, KedaAutoscalingPollingIntervalAnnotation, defaultKedaPollingInterval)
setDefaultAnnotationIfNotPresent(obj, KedaAutoscalingCooldownPeriodAnnotation, defaultKedaCooldownPeriod)
setDefaultAnnotationIfNotPresent(obj, KedaAutoscalingSubscriptionSizeAnnotation, defaultKedaSubscriptionSize)
// If it wasn't configured, then delete any autoscaling related configuration.
} else {
deleteAnnotationIfPresent(obj, AutoscalingMinScaleAnnotation)
deleteAnnotationIfPresent(obj, AutoscalingMaxScaleAnnotation)
deleteAnnotationIfPresent(obj, KedaAutoscalingPollingIntervalAnnotation)
deleteAnnotationIfPresent(obj, KedaAutoscalingCooldownPeriodAnnotation)
deleteAnnotationIfPresent(obj, KedaAutoscalingSubscriptionSizeAnnotation)
}
}
// ValidateAutoscalingAnnotations validates the autoscaling annotations.
// The class ensures that we reconcile using the corresponding controller.
func ValidateAutoscalingAnnotations(ctx context.Context, annotations map[string]string, errs *apis.FieldError) *apis.FieldError {
if autoscalingClass, ok := annotations[AutoscalingClassAnnotation]; ok {
// Only supported autoscaling class is KEDA.
if autoscalingClass != KEDA {
errs = errs.Also(apis.ErrInvalidValue(autoscalingClass, fmt.Sprintf("metadata.annotations[%s]", AutoscalingClassAnnotation)))
}
var minScale, maxScale int
minScale, errs = validateAnnotation(annotations, AutoscalingMinScaleAnnotation, minimumMinScale, errs)
maxScale, errs = validateAnnotation(annotations, AutoscalingMaxScaleAnnotation, minimumMaxScale, errs)
if maxScale < minScale {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("maxScale=%d is less than minScale=%d", maxScale, minScale),
Paths: []string{fmt.Sprintf("metadata.annotations[%s]", AutoscalingMaxScaleAnnotation), fmt.Sprintf("[%s]", AutoscalingMinScaleAnnotation)},
})
}
_, errs = validateAnnotation(annotations, KedaAutoscalingPollingIntervalAnnotation, minimumKedaPollingInterval, errs)
_, errs = validateAnnotation(annotations, KedaAutoscalingCooldownPeriodAnnotation, minimumKedaCooldownPeriod, errs)
_, errs = validateAnnotation(annotations, KedaAutoscalingSubscriptionSizeAnnotation, minimumKedaSubscriptionSize, errs)
} else {
errs = validateAnnotationNotExists(annotations, AutoscalingMinScaleAnnotation, errs)
errs = validateAnnotationNotExists(annotations, AutoscalingMaxScaleAnnotation, errs)
errs = validateAnnotationNotExists(annotations, KedaAutoscalingPollingIntervalAnnotation, errs)
errs = validateAnnotationNotExists(annotations, KedaAutoscalingCooldownPeriodAnnotation, errs)
errs = validateAnnotationNotExists(annotations, KedaAutoscalingSubscriptionSizeAnnotation, errs)
}
return errs
}
func validateAnnotation(annotations map[string]string, annotation string, minimumValue int, errs *apis.FieldError) (int, *apis.FieldError) {
var value int
if val, ok := annotations[annotation]; !ok {
errs = errs.Also(apis.ErrMissingField(fmt.Sprintf("metadata.annotations[%s]", annotation)))
} else if v, err := strconv.Atoi(val); err != nil {
errs = errs.Also(apis.ErrInvalidValue(val, fmt.Sprintf("metadata.annotations[%s]", annotation)))
} else if v < minimumValue {
errs = errs.Also(apis.ErrOutOfBoundsValue(v, minimumValue, math.MaxInt32, fmt.Sprintf("metadata.annotations[%s]", annotation)))
} else {
value = v
}
return value, errs
}
func setDefaultAnnotationIfNotPresent(obj *metav1.ObjectMeta, annotation string, defaultValue string) {
if _, ok := obj.Annotations[annotation]; !ok {
obj.Annotations[annotation] = defaultValue
}
}
func deleteAnnotationIfPresent(obj *metav1.ObjectMeta, annotation string) {
if _, ok := obj.Annotations[annotation]; ok {
delete(obj.Annotations, annotation)
}
}
func validateAnnotationNotExists(annotations map[string]string, annotation string, errs *apis.FieldError) *apis.FieldError {
if _, ok := annotations[annotation]; ok {
errs = errs.Also(apis.ErrDisallowedFields(fmt.Sprintf("metadata.annotations[%s]", annotation)))
}
return errs
}