Skip to content

Commit 445d09d

Browse files
authored
Fix validation bug in FleetAutoscaler (googleforgames#2242)
* Fix validation bug in FleetAutoscaler * Fix bug when sync was not set for FleetAutoscaler * Regenerate install.yaml from helm template * Add test for CustomFasSyncInterval in FleetAutoscaler
1 parent 4bf3398 commit 445d09d

File tree

7 files changed

+260
-3
lines changed

7 files changed

+260
-3
lines changed

‎install/helm/agones/templates/extensions.yaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ webhooks:
137137
- "v1"
138138
operations:
139139
- CREATE
140+
- apiGroups:
141+
- autoscaling.agones.dev
142+
resources:
143+
- "fleetautoscalers"
144+
apiVersions:
145+
- "v1"
146+
operations:
147+
- CREATE
148+
- UPDATE
140149
{{- end }}
141150
---
142151
apiVersion: v1

‎install/yaml/install.yaml‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13591,7 +13591,16 @@ webhooks:
1359113591
apiVersions:
1359213592
- "v1"
1359313593
operations:
13594-
- CREATE
13594+
- CREATE
13595+
- apiGroups:
13596+
- autoscaling.agones.dev
13597+
resources:
13598+
- "fleetautoscalers"
13599+
apiVersions:
13600+
- "v1"
13601+
operations:
13602+
- CREATE
13603+
- UPDATE
1359513604
---
1359613605
# Source: agones/templates/priority-class.yaml
1359713606
apiVersion: scheduling.k8s.io/v1

‎pkg/apis/autoscaling/v1/fleetautoscaler.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ func (fas *FleetAutoscaler) Validate(causes []metav1.StatusCause) []metav1.Statu
204204
causes=fas.Spec.Policy.Webhook.ValidateWebhookPolicy(causes)
205205
}
206206

207-
causes=fas.Spec.Sync.FixedInterval.ValidateFixedIntervalSync(causes)
207+
ifruntime.FeatureEnabled(runtime.FeatureCustomFasSyncInterval){
208+
causes=fas.Spec.Sync.FixedInterval.ValidateFixedIntervalSync(causes)
209+
}
208210
returncauses
209211
}
210212

‎pkg/apis/autoscaling/v1/fleetautoscaler_test.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package v1
1717
import (
1818
"testing"
1919

20+
"agones.dev/agones/pkg/util/runtime"
2021
"github.com/stretchr/testify/assert"
2122
admregv1 "k8s.io/api/admissionregistration/v1"
2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -114,6 +115,11 @@ func TestFleetAutoscalerValidateUpdate(t *testing.T){
114115
})
115116

116117
t.Run("bad sync interval seconds", func(t*testing.T){
118+
if!runtime.FeatureEnabled(runtime.FeatureCustomFasSyncInterval){
119+
// Do not run test if FeatureCustomFasSyncInterval is not enabled
120+
t.Skip()
121+
}
122+
117123
fas:=defaultFixture()
118124
fas.Spec.Sync.FixedInterval.Seconds=0
119125

‎pkg/fleetautoscalers/controller.go‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"agones.dev/agones/pkg/util/webhooks"
3737
"agones.dev/agones/pkg/util/workerqueue"
3838
"github.com/heptiolabs/healthcheck"
39+
"github.com/mattbaird/jsonpatch"
3940
"github.com/pkg/errors"
4041
"github.com/sirupsen/logrus"
4142
admissionv1 "k8s.io/api/admission/v1"
@@ -107,6 +108,8 @@ func NewController(
107108
c.recorder=eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "fleetautoscaler-controller"})
108109

109110
kind:=autoscalingv1.Kind("FleetAutoscaler")
111+
wh.AddHandler("/mutate", kind, admissionv1.Create, c.mutationHandler)
112+
wh.AddHandler("/mutate", kind, admissionv1.Update, c.mutationHandler)
110113
wh.AddHandler("/validate", kind, admissionv1.Create, c.validationHandler)
111114
wh.AddHandler("/validate", kind, admissionv1.Update, c.validationHandler)
112115

@@ -165,6 +168,41 @@ func (c *Controller) loggerForFleetAutoscaler(fas *autoscalingv1.FleetAutoscaler
165168
returnc.loggerForFleetAutoscalerKey(fasName).WithField("fas", fas)
166169
}
167170

171+
// creationMutationHandler is the handler for the mutating webhook that sets the
172+
// the default values on the FleetAutoscaler
173+
func (c*Controller) mutationHandler(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error){
174+
obj:=review.Request.Object
175+
fas:=&autoscalingv1.FleetAutoscaler{}
176+
err:=json.Unmarshal(obj.Raw, fas)
177+
iferr!=nil{
178+
c.baseLogger.WithField("review", review).WithError(err).Error("validationHandler")
179+
returnreview, errors.Wrapf(err, "error unmarshalling original FleetAutoscaler json: %s", obj.Raw)
180+
}
181+
182+
fas.ApplyDefaults()
183+
184+
newFas, err:=json.Marshal(fas)
185+
iferr!=nil{
186+
returnreview, errors.Wrapf(err, "error marshalling default applied FleetAutoscaler %s to json", fas.ObjectMeta.Name)
187+
}
188+
189+
patch, err:=jsonpatch.CreatePatch(obj.Raw, newFas)
190+
iferr!=nil{
191+
returnreview, errors.Wrapf(err, "error creating patch for FleetAutoscaler %s", fas.ObjectMeta.Name)
192+
}
193+
194+
jsonPatch, err:=json.Marshal(patch)
195+
iferr!=nil{
196+
returnreview, errors.Wrapf(err, "error creating json for patch for FleetAutoScaler %s", fas.ObjectMeta.Name)
197+
}
198+
199+
pt:=admissionv1.PatchTypeJSONPatch
200+
review.Response.PatchType=&pt
201+
review.Response.Patch=jsonPatch
202+
203+
returnreview, nil
204+
}
205+
168206
// validationHandler will intercept when a FleetAutoscaler is created, and
169207
// validate its settings.
170208
func (c*Controller) validationHandler(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error){

‎pkg/fleetautoscalers/controller_test.go‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import (
3030
utilruntime "agones.dev/agones/pkg/util/runtime"
3131
"agones.dev/agones/pkg/util/webhooks"
3232
"github.com/heptiolabs/healthcheck"
33+
"github.com/mattbaird/jsonpatch"
3334
"github.com/pkg/errors"
3435
"github.com/stretchr/testify/assert"
36+
"github.com/stretchr/testify/require"
3537
admissionv1 "k8s.io/api/admission/v1"
3638
admregv1 "k8s.io/api/admissionregistration/v1"
3739
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -63,6 +65,142 @@ var (
6365
gvk=metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("FleetAutoscaler"))
6466
)
6567

68+
funcTestControllerCreationMutationHandler(t*testing.T){
69+
t.Parallel()
70+
71+
typeexpectedstruct{
72+
responseAllowedbool
73+
patches []jsonpatch.JsonPatchOperation
74+
errstring
75+
}
76+
77+
vartestCases= []struct{
78+
descriptionstring
79+
fixtureinterface{}
80+
expectedexpected
81+
}{
82+
{
83+
description: "OK",
84+
fixture: &autoscalingv1.FleetAutoscaler{
85+
ObjectMeta: metav1.ObjectMeta{
86+
Name: "fas-1",
87+
Namespace: "default",
88+
Generation: 2,
89+
},
90+
Spec: autoscalingv1.FleetAutoscalerSpec{
91+
FleetName: "fleet-1",
92+
Policy: autoscalingv1.FleetAutoscalerPolicy{
93+
Type: autoscalingv1.BufferPolicyType,
94+
Buffer: &autoscalingv1.BufferPolicy{
95+
BufferSize: intstr.FromInt(5),
96+
MaxReplicas: 100,
97+
},
98+
},
99+
Sync: &autoscalingv1.FleetAutoscalerSync{
100+
Type: autoscalingv1.FixedIntervalSyncType,
101+
FixedInterval: autoscalingv1.FixedIntervalSync{
102+
Seconds: 30,
103+
},
104+
},
105+
},
106+
},
107+
expected: expected{
108+
responseAllowed: true,
109+
patches: []jsonpatch.JsonPatchOperation{},
110+
},
111+
},
112+
{
113+
description: "OK",
114+
// Spec.Sync is not defined
115+
fixture: &autoscalingv1.FleetAutoscaler{
116+
ObjectMeta: metav1.ObjectMeta{
117+
Name: "fas-1",
118+
Namespace: "default",
119+
Generation: 2,
120+
},
121+
Spec: autoscalingv1.FleetAutoscalerSpec{
122+
FleetName: "fleet-1",
123+
Policy: autoscalingv1.FleetAutoscalerPolicy{
124+
Type: autoscalingv1.BufferPolicyType,
125+
Buffer: &autoscalingv1.BufferPolicy{
126+
BufferSize: intstr.FromInt(5),
127+
MaxReplicas: 100,
128+
},
129+
},
130+
},
131+
},
132+
expected: expected{
133+
responseAllowed: true,
134+
patches: []jsonpatch.JsonPatchOperation{
135+
{
136+
Operation: "add",
137+
Path: "/spec/sync",
138+
Value: map[string]interface{}{
139+
"fixedInterval": map[string]interface{}{
140+
"seconds": float64(30),
141+
},
142+
"type": "FixedInterval",
143+
},
144+
},
145+
},
146+
},
147+
},
148+
{
149+
description: "Wrong request object, err expected",
150+
fixture: "WRONG DATA",
151+
expected: expected{
152+
err: `error unmarshalling original FleetAutoscaler json: "WRONG DATA": json: cannot unmarshal string into Go value of type v1.FleetAutoscaler`,
153+
},
154+
},
155+
}
156+
157+
c, _:=newFakeController()
158+
159+
for_, tc:=rangetestCases{
160+
t.Run(tc.description, func(t*testing.T){
161+
raw, err:=json.Marshal(tc.fixture)
162+
require.NoError(t, err)
163+
164+
review:= admissionv1.AdmissionReview{
165+
Request: &admissionv1.AdmissionRequest{
166+
Kind: gvk,
167+
Operation: admissionv1.Create,
168+
Object: runtime.RawExtension{
169+
Raw: raw,
170+
},
171+
},
172+
Response: &admissionv1.AdmissionResponse{Allowed: true},
173+
}
174+
175+
result, err:=c.mutationHandler(review)
176+
177+
iferr!=nil&&tc.expected.err!=""{
178+
require.Equal(t, tc.expected.err, err.Error())
179+
} else{
180+
assert.True(t, result.Response.Allowed)
181+
assert.Equal(t, admissionv1.PatchTypeJSONPatch, *result.Response.PatchType)
182+
183+
patch:=&jsonpatch.ByPath{}
184+
err=json.Unmarshal(result.Response.Patch, patch)
185+
require.NoError(t, err)
186+
187+
ifutilruntime.FeatureEnabled(utilruntime.FeatureCustomFasSyncInterval){
188+
found:=false
189+
190+
for_, expected:=rangetc.expected.patches{
191+
for_, p:=range*patch{
192+
ifassert.ObjectsAreEqual(p, expected){
193+
found=true
194+
}
195+
}
196+
assert.True(t, found, "Could not find operation %#v in patch %v", expected, *patch)
197+
}
198+
}
199+
}
200+
})
201+
}
202+
}
203+
66204
funcTestControllerCreationValidationHandler(t*testing.T){
67205
t.Parallel()
68206

‎test/e2e/fleetautoscaler_test.go‎

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
2626
autoscalingv1 "agones.dev/agones/pkg/apis/autoscaling/v1"
27+
"agones.dev/agones/pkg/util/runtime"
2728
e2e "agones.dev/agones/test/e2e/framework"
2829
"github.com/pkg/errors"
2930
"github.com/sirupsen/logrus"
@@ -60,7 +61,8 @@ func TestAutoscalerBasicFunctions(t *testing.T){
6061
framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
6162

6263
fleetautoscalers:=framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
63-
fas, err:=fleetautoscalers.Create(ctx, defaultFleetAutoscaler(flt, framework.Namespace), metav1.CreateOptions{})
64+
defaultFas:=defaultFleetAutoscaler(flt, framework.Namespace)
65+
fas, err:=fleetautoscalers.Create(ctx, defaultFas, metav1.CreateOptions{})
6466
ifassert.Nil(t, err){
6567
deferfleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
6668
} else{
@@ -69,6 +71,11 @@ func TestAutoscalerBasicFunctions(t *testing.T){
6971
return
7072
}
7173

74+
// If the CustomFasSyncInterval feature flag is enabled, the value of fas.spec.sync are equal
75+
ifruntime.FeatureEnabled(runtime.FeatureCustomFasSyncInterval){
76+
assert.Equal(t, defaultFas.Spec.Sync.FixedInterval.Seconds, fas.Spec.Sync.FixedInterval.Seconds)
77+
}
78+
7279
// the fleet autoscaler should scale the fleet up now up to BufferSize
7380
bufferSize:=int32(fas.Spec.Policy.Buffer.BufferSize.IntValue())
7481
framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(bufferSize))
@@ -132,6 +139,54 @@ func TestAutoscalerBasicFunctions(t *testing.T){
132139
})
133140
}
134141

142+
funcTestFleetAutoscalerDefaultSyncInterval(t*testing.T){
143+
t.Parallel()
144+
ctx:=context.Background()
145+
146+
stable:=framework.AgonesClient.AgonesV1()
147+
fleets:=stable.Fleets(framework.Namespace)
148+
flt, err:=fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
149+
ifassert.Nil(t, err){
150+
deferfleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
151+
}
152+
153+
framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
154+
155+
fleetautoscalers:=framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
156+
dummyFleetName:="dummy-fleet"
157+
defaultFas:=&autoscalingv1.FleetAutoscaler{
158+
ObjectMeta: metav1.ObjectMeta{
159+
Name: dummyFleetName+"-autoscaler",
160+
Namespace: framework.Namespace,
161+
},
162+
Spec: autoscalingv1.FleetAutoscalerSpec{
163+
FleetName: dummyFleetName,
164+
Policy: autoscalingv1.FleetAutoscalerPolicy{
165+
Type: autoscalingv1.BufferPolicyType,
166+
Buffer: &autoscalingv1.BufferPolicy{
167+
BufferSize: intstr.FromInt(3),
168+
MaxReplicas: 10,
169+
},
170+
},
171+
},
172+
}
173+
fas, err:=fleetautoscalers.Create(ctx, defaultFas, metav1.CreateOptions{})
174+
ifassert.Nil(t, err){
175+
deferfleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
176+
} else{
177+
// if we could not create the autoscaler, their is no point going further
178+
logrus.Error("Failed creating autoscaler, aborting TestFleetAutoscalerDefaultSyncInterval")
179+
return
180+
}
181+
182+
// If the CustomFasSyncInterval feature flag is enabled, fas.spec.sync should be set to its default value
183+
ifruntime.FeatureEnabled(runtime.FeatureCustomFasSyncInterval){
184+
defaultSyncIntervalFas:=&autoscalingv1.FleetAutoscaler{}
185+
defaultSyncIntervalFas.ApplyDefaults()
186+
assert.Equal(t, defaultSyncIntervalFas.Spec.Sync.FixedInterval.Seconds, fas.Spec.Sync.FixedInterval.Seconds)
187+
}
188+
}
189+
135190
// TestFleetAutoScalerRollingUpdate - test fleet with RollingUpdate strategy work with
136191
// FleetAutoscaler, verify that number of GameServers does not goes down below RollingUpdate strategy
137192
// defined level on Fleet updates.

0 commit comments

Comments
(0)