Skip to content

Commit 55f4b8c

Browse files
committed
add replicas.idle to set idleReplicaCount on SO
Signed-off-by: Luca Kuendig <[email protected]>
1 parent 2b4accc commit 55f4b8c

File tree

10 files changed

+144
-75
lines changed

10 files changed

+144
-75
lines changed

Diff for: config/crd/bases/http.keda.sh_httpscaledobjects.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ spec:
3333
- jsonPath: .spec.replicas.max
3434
name: MaxReplicas
3535
type: integer
36+
- jsonPath: .spec.replicas.idle
37+
name: IdleReplicas
38+
type: integer
3639
- jsonPath: .metadata.creationTimestamp
3740
name: Age
3841
type: date
@@ -66,6 +69,11 @@ spec:
6669
replicas:
6770
description: (optional) Replica information
6871
properties:
72+
idle:
73+
description: Amount of replicas to have in the deployment while
74+
being idle
75+
format: int32
76+
type: integer
6977
max:
7078
description: Maximum amount of replicas to have in the deployment
7179
(Default 100)

Diff for: e2e/k8s.go

+24-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55

66
"github.com/codeskyblue/go-sh"
7-
kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
87
"github.com/pkg/errors"
98
"k8s.io/client-go/rest"
109
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -33,7 +32,28 @@ func deleteNS(ns string) error {
3332
return sh.Command("kubectl", "delete", "namespace", ns).Run()
3433
}
3534

36-
func getScaledObject(ctx context.Context, cl client.Client, ns string, name string) error {
37-
var scaledObject kedav1alpha1.ScaledObject
38-
return cl.Get(ctx, k8s.ObjKey(ns, name), &scaledObject)
35+
func getScaledObject(
36+
ctx context.Context,
37+
cl client.Client,
38+
ns,
39+
name string,
40+
) error {
41+
scaledObject, err := k8s.NewScaledObject(
42+
ns,
43+
name,
44+
"",
45+
"",
46+
"",
47+
1,
48+
2,
49+
0,
50+
30,
51+
)
52+
if err != nil {
53+
return err
54+
}
55+
if err := cl.Get(ctx, k8s.ObjKey(ns, name), scaledObject); err != nil {
56+
return err
57+
}
58+
return nil
3959
}

Diff for: examples/xkcd/templates/httpscaledobject.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ spec:
1212
replicas:
1313
min: {{ .Values.autoscaling.http.minReplicas }}
1414
max: {{ .Values.autoscaling.http.maxReplicas }}
15+
idle: {{ .Values.autoscaling.http.idleReplicas }}

Diff for: examples/xkcd/values.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ autoscaling:
4646
http:
4747
minReplicas: 0
4848
maxReplicas: 10
49+
idleReplicas: 0

Diff for: operator/api/v1alpha1/httpscaledobject_types.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ type ReplicaStruct struct {
8383
// Minimum amount of replicas to have in the deployment (Default 0)
8484
Min *int32 `json:"min,omitempty" description:"Minimum amount of replicas to have in the deployment (Default 0)"`
8585
// Maximum amount of replicas to have in the deployment (Default 100)
86-
Max *int32 `json:"max,omitempty" description:"Maximum amount of replicas to have in the deployment (Default 100)"`
86+
Max int32 `json:"max,omitempty" description:"Maximum amount of replicas to have in the deployment (Default 100)"`
87+
// Amount of replicas to have in the deployment while being idle
88+
Idle int32 `json:"idle,omitempty" description:"Amount of replicas to have in the deployment while being idle"`
8789
}
8890

8991
// HTTPScaledObjectSpec defines the desired state of HTTPScaledObject
@@ -133,6 +135,7 @@ type HTTPScaledObjectStatus struct {
133135
// +kubebuilder:printcolumn:name="ScaleTargetPort",type="integer",JSONPath=".spec.scaleTargetRef"
134136
// +kubebuilder:printcolumn:name="MinReplicas",type="integer",JSONPath=".spec.replicas.min"
135137
// +kubebuilder:printcolumn:name="MaxReplicas",type="integer",JSONPath=".spec.replicas.max"
138+
// +kubebuilder:printcolumn:name="IdleReplicas",type="integer",JSONPath=".spec.replicas.idle"
136139
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
137140
// +kubebuilder:printcolumn:name="Active",type="string",JSONPath=".status.conditions[?(@.type==\"HTTPScaledObjectIsReady\")].status"
138141

Diff for: operator/controllers/scaled_object.go

+5-11
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,16 @@ func createOrUpdateScaledObject(
2626
httpso *v1alpha1.HTTPScaledObject,
2727
) error {
2828
logger.Info("Creating scaled objects", "external scaler host name", externalScalerHostName)
29-
30-
var minReplicaCount *int32
31-
var maxReplicaCount *int32
32-
if replicas := httpso.Spec.Replicas; replicas != nil {
33-
minReplicaCount = replicas.Min
34-
maxReplicaCount = replicas.Max
35-
}
36-
37-
appScaledObject := k8s.NewScaledObject(
29+
logger.Info("This is the current httpso object", "output", httpso.Spec)
30+
appScaledObject, appErr := k8s.NewScaledObject(
3831
httpso.GetNamespace(),
3932
fmt.Sprintf("%s-app", httpso.GetName()), // HTTPScaledObject name is the same as the ScaledObject name
4033
httpso.Spec.ScaleTargetRef.Deployment,
4134
externalScalerHostName,
4235
httpso.Spec.Host,
43-
minReplicaCount,
44-
maxReplicaCount,
36+
httpso.Spec.Replicas.Min,
37+
httpso.Spec.Replicas.Max,
38+
httpso.Spec.Replicas.Idle,
4539
httpso.Spec.CooldownPeriod,
4640
)
4741

Diff for: operator/controllers/scaled_object_test.go

+19-18
Original file line numberDiff line numberDiff line change
@@ -56,32 +56,29 @@ func TestCreateOrUpdateScaledObject(t *testing.T) {
5656
metadata.Name,
5757
)
5858

59-
var minReplicaCount *int32
60-
var maxReplicaCount *int32
61-
if replicas := testInfra.httpso.Spec.Replicas; replicas != nil {
62-
minReplicaCount = replicas.Min
63-
maxReplicaCount = replicas.Max
64-
}
65-
66-
r.EqualValues(
67-
minReplicaCount,
68-
spec.MinReplicaCount,
59+
// HTTPScaledObject min/max/idle replicas are int32s,
60+
// but the ScaledObject's spec is decoded into
61+
// an *unsructured.Unstructured (basically a map[string]interface{})
62+
// which is an int64. we need to convert the
63+
// HTTPScaledObject's values into int64s before we compare
64+
r.Equal(
65+
int64(testInfra.httpso.Spec.Replicas.Min),
66+
spec["minReplicaCount"],
6967
)
7068
r.EqualValues(
7169
maxReplicaCount,
7270
spec.MaxReplicaCount,
7371
)
72+
r.Equal(
73+
int64(testInfra.httpso.Spec.Replicas.Idle),
74+
spec["idleReplicaCount"],
75+
)
7476

7577
// now update the min and max replicas on the httpso
7678
// and call createOrUpdateScaledObject again
77-
if spec := &testInfra.httpso.Spec; spec.Replicas == nil {
78-
spec.Replicas = &v1alpha1.ReplicaStruct{
79-
Min: new(int32),
80-
Max: new(int32),
81-
}
82-
}
83-
*testInfra.httpso.Spec.Replicas.Min++
84-
*testInfra.httpso.Spec.Replicas.Max++
79+
testInfra.httpso.Spec.Replicas.Min++
80+
testInfra.httpso.Spec.Replicas.Max++
81+
testInfra.httpso.Spec.Replicas.Idle++
8582
r.NoError(createOrUpdateScaledObject(
8683
testInfra.ctx,
8784
testInfra.cl,
@@ -107,6 +104,10 @@ func TestCreateOrUpdateScaledObject(t *testing.T) {
107104
*testInfra.httpso.Spec.Replicas.Max,
108105
*spec.MaxReplicaCount,
109106
)
107+
r.Equal(
108+
int64(testInfra.httpso.Spec.Replicas.Idle),
109+
spec["idleReplicaCount"],
110+
)
110111
}
111112

112113
func getSO(

Diff for: operator/controllers/suite_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
logf "sigs.k8s.io/controller-runtime/pkg/log"
3232
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3333

34+
"github.com/kedacore/http-add-on/operator/api/v1alpha1"
3435
httpv1alpha1 "github.com/kedacore/http-add-on/operator/api/v1alpha1"
3536
// +kubebuilder:scaffold:imports
3637
)
@@ -85,6 +86,11 @@ func newCommonTestInfra(namespace, appName string) *commonTestInfra {
8586
Service: appName,
8687
Port: 8081,
8788
},
89+
Replicas: v1alpha1.ReplicaStruct{
90+
Min: 0,
91+
Max: 20,
92+
Idle: 0,
93+
},
8894
},
8995
}
9096

Diff for: pkg/k8s/scaledobject.go

+51-41
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package k8s
22

33
import (
4-
kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
5-
appsv1 "k8s.io/api/apps/v1"
6-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7-
"k8s.io/utils/pointer"
4+
"bytes"
5+
"text/template"
6+
7+
"gopkg.in/yaml.v2"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
89
)
910

1011
const (
@@ -22,42 +23,51 @@ func NewScaledObject(
2223
deploymentName string,
2324
scalerAddress string,
2425
host string,
25-
minReplicas *int32,
26-
maxReplicas *int32,
27-
cooldownPeriod *int32,
28-
) *kedav1alpha1.ScaledObject {
29-
return &kedav1alpha1.ScaledObject{
30-
TypeMeta: metav1.TypeMeta{
31-
APIVersion: kedav1alpha1.SchemeGroupVersion.Identifier(),
32-
Kind: ObjectKind(&kedav1alpha1.ScaledObject{}),
33-
},
34-
ObjectMeta: metav1.ObjectMeta{
35-
Namespace: namespace,
36-
Name: name,
37-
Labels: Labels(name),
38-
},
39-
Spec: kedav1alpha1.ScaledObjectSpec{
40-
ScaleTargetRef: &kedav1alpha1.ScaleTarget{
41-
APIVersion: appsv1.SchemeGroupVersion.Identifier(),
42-
Kind: ObjectKind(&appsv1.Deployment{}),
43-
Name: deploymentName,
44-
},
45-
PollingInterval: pointer.Int32(soPollingInterval),
46-
CooldownPeriod: cooldownPeriod,
47-
MinReplicaCount: minReplicas,
48-
MaxReplicaCount: maxReplicas,
49-
Advanced: &kedav1alpha1.AdvancedConfig{
50-
RestoreToOriginalReplicaCount: true,
51-
},
52-
Triggers: []kedav1alpha1.ScaleTriggers{
53-
{
54-
Type: soTriggerType,
55-
Metadata: map[string]string{
56-
mkScalerAddress: scalerAddress,
57-
mkHost: host,
58-
},
59-
},
60-
},
61-
},
26+
minReplicas,
27+
maxReplicas int32,
28+
idleReplicas int32,
29+
cooldownPeriod int32,
30+
) (*unstructured.Unstructured, error) {
31+
// https://keda.sh/docs/1.5/faq/
32+
// https://github.com/kedacore/keda/blob/aa0ea79450a1c7549133aab46f5b916efa2364ab/api/v1alpha1/scaledobject_types.go
33+
//
34+
// unstructured.Unstructured only supports specific types in it. see here for the list:
35+
// https://github.com/kubernetes/apimachinery/blob/v0.17.12/pkg/runtime/converter.go#L449-L476
36+
typedLabels := Labels(name)
37+
labels := map[string]interface{}{}
38+
for k, v := range typedLabels {
39+
var vIface interface{} = v
40+
labels[k] = vIface
41+
}
42+
43+
tpl, err := template.ParseFS(scaledObjectTemplateFS, "templates/scaledobject.yaml")
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
var scaledObjectTemplateBuffer bytes.Buffer
49+
if tplErr := tpl.Execute(&scaledObjectTemplateBuffer, map[string]interface{}{
50+
"Name": name,
51+
"Namespace": namespace,
52+
"Labels": labels,
53+
"MinReplicas": minReplicas,
54+
"MaxReplicas": maxReplicas,
55+
"IdleReplicas": idleReplicas,
56+
"DeploymentName": deploymentName,
57+
"ScalerAddress": scalerAddress,
58+
"Host": host,
59+
"CooldownPeriod": cooldownPeriod,
60+
}); tplErr != nil {
61+
return nil, tplErr
62+
}
63+
64+
var decodedYaml map[string]interface{}
65+
decodeErr := yaml.Unmarshal(scaledObjectTemplateBuffer.Bytes(), &decodedYaml)
66+
if decodeErr != nil {
67+
return nil, decodeErr
6268
}
69+
70+
return &unstructured.Unstructured{
71+
Object: decodedYaml,
72+
}, nil
6373
}

Diff for: pkg/k8s/templates/scaledobject.yaml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
apiVersion: keda.sh/v1alpha1
2+
kind: ScaledObject
3+
metadata:
4+
name: {{ .Name }}
5+
namespace: {{ .Namespace }}
6+
labels:
7+
{{- range $key, $val := .Labels }}
8+
{{ $key }}: {{ $val }}
9+
{{- end }}
10+
spec:
11+
minReplicaCount: {{ .MinReplicas }}
12+
maxReplicaCount: {{ .MaxReplicas }}
13+
idleReplicaCount: {{ .IdleReplicas }}
14+
cooldownPeriod: {{ .CooldownPeriod }}
15+
pollingInterval: 1
16+
scaleTargetRef:
17+
name: {{ .DeploymentName }}
18+
kind: Deployment
19+
triggers:
20+
- type: external-push
21+
metadata:
22+
scalerAddress: {{ .ScalerAddress }}
23+
host: {{ .Host }}
24+
advanced:
25+
restoreToOriginalReplicaCount: true

0 commit comments

Comments
 (0)