agones.dev/agones@v1.53.0/pkg/fleetautoscalers/controller_test.go (about) 1 // Copyright 2018 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fleetautoscalers 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "net/http" 22 "net/http/httptest" 23 "sync/atomic" 24 "testing" 25 "time" 26 27 "agones.dev/agones/pkg/apis" 28 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 29 autoscalingv1 "agones.dev/agones/pkg/apis/autoscaling/v1" 30 "agones.dev/agones/pkg/gameservers" 31 agtesting "agones.dev/agones/pkg/testing" 32 "agones.dev/agones/pkg/util/webhooks" 33 "github.com/heptiolabs/healthcheck" 34 "github.com/pkg/errors" 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/require" 37 "gomodules.xyz/jsonpatch/v2" 38 admissionv1 "k8s.io/api/admission/v1" 39 admregv1 "k8s.io/api/admissionregistration/v1" 40 corev1 "k8s.io/api/core/v1" 41 k8serrors "k8s.io/apimachinery/pkg/api/errors" 42 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 43 "k8s.io/apimachinery/pkg/runtime" 44 "k8s.io/apimachinery/pkg/types" 45 "k8s.io/apimachinery/pkg/util/intstr" 46 "k8s.io/apimachinery/pkg/watch" 47 k8stesting "k8s.io/client-go/testing" 48 "k8s.io/client-go/tools/cache" 49 ) 50 51 var ( 52 gvk = metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("FleetAutoscaler")) 53 ) 54 55 func TestControllerCreationMutationHandler(t *testing.T) { 56 t.Parallel() 57 58 type expected struct { 59 responseAllowed bool 60 patches []jsonpatch.JsonPatchOperation 61 nilPatch bool 62 } 63 64 var testCases = []struct { 65 description string 66 fixture interface{} 67 expected expected 68 }{ 69 { 70 description: "OK", 71 fixture: &autoscalingv1.FleetAutoscaler{ 72 ObjectMeta: metav1.ObjectMeta{ 73 Name: "fas-1", 74 Namespace: "default", 75 Generation: 2, 76 }, 77 Spec: autoscalingv1.FleetAutoscalerSpec{ 78 FleetName: "fleet-1", 79 Policy: autoscalingv1.FleetAutoscalerPolicy{ 80 Type: autoscalingv1.BufferPolicyType, 81 Buffer: &autoscalingv1.BufferPolicy{ 82 BufferSize: intstr.FromInt(5), 83 MaxReplicas: 100, 84 }, 85 }, 86 Sync: &autoscalingv1.FleetAutoscalerSync{ 87 Type: autoscalingv1.FixedIntervalSyncType, 88 FixedInterval: autoscalingv1.FixedIntervalSync{ 89 Seconds: 30, 90 }, 91 }, 92 }, 93 }, 94 expected: expected{ 95 responseAllowed: true, 96 patches: []jsonpatch.JsonPatchOperation{}, 97 }, 98 }, 99 { 100 description: "OK", 101 // Spec.Sync is not defined 102 fixture: &autoscalingv1.FleetAutoscaler{ 103 ObjectMeta: metav1.ObjectMeta{ 104 Name: "fas-1", 105 Namespace: "default", 106 Generation: 2, 107 }, 108 Spec: autoscalingv1.FleetAutoscalerSpec{ 109 FleetName: "fleet-1", 110 Policy: autoscalingv1.FleetAutoscalerPolicy{ 111 Type: autoscalingv1.BufferPolicyType, 112 Buffer: &autoscalingv1.BufferPolicy{ 113 BufferSize: intstr.FromInt(5), 114 MaxReplicas: 100, 115 }, 116 }, 117 }, 118 }, 119 expected: expected{ 120 responseAllowed: true, 121 patches: []jsonpatch.JsonPatchOperation{ 122 { 123 Operation: "add", 124 Path: "/spec/sync", 125 Value: map[string]interface{}{ 126 "fixedInterval": map[string]interface{}{ 127 "seconds": float64(30), 128 }, 129 "type": "FixedInterval", 130 }, 131 }, 132 }, 133 }, 134 }, 135 { 136 description: "Wrong request object, err expected", 137 fixture: "WRONG DATA", 138 expected: expected{nilPatch: true}, 139 }, 140 } 141 142 ext := newFakeExtensions() 143 144 for _, tc := range testCases { 145 t.Run(tc.description, func(t *testing.T) { 146 raw, err := json.Marshal(tc.fixture) 147 require.NoError(t, err) 148 149 review := admissionv1.AdmissionReview{ 150 Request: &admissionv1.AdmissionRequest{ 151 Kind: gvk, 152 Operation: admissionv1.Create, 153 Object: runtime.RawExtension{ 154 Raw: raw, 155 }, 156 }, 157 Response: &admissionv1.AdmissionResponse{Allowed: true}, 158 } 159 160 result, err := ext.mutationHandler(review) 161 162 assert.NoError(t, err) 163 if tc.expected.nilPatch { 164 require.Nil(t, result.Response.PatchType) 165 } else { 166 assert.True(t, result.Response.Allowed) 167 assert.Equal(t, admissionv1.PatchTypeJSONPatch, *result.Response.PatchType) 168 169 patch := &jsonpatch.ByPath{} 170 err = json.Unmarshal(result.Response.Patch, patch) 171 require.NoError(t, err) 172 173 found := false 174 175 for _, expected := range tc.expected.patches { 176 for _, p := range *patch { 177 if assert.ObjectsAreEqual(p, expected) { 178 found = true 179 } 180 } 181 assert.True(t, found, "Could not find operation %#v in patch %v", expected, *patch) 182 } 183 } 184 }) 185 } 186 } 187 188 func TestControllerCreationValidationHandler(t *testing.T) { 189 t.Parallel() 190 191 t.Run("valid fleet autoscaler", func(t *testing.T) { 192 _, m := newFakeController() 193 ext := newFakeExtensions() 194 fas, _ := defaultFixtures() 195 _, cancel := agtesting.StartInformers(m) 196 defer cancel() 197 198 review, err := newAdmissionReview(*fas) 199 assert.Nil(t, err) 200 201 result, err := ext.validationHandler(review) 202 assert.Nil(t, err) 203 assert.True(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response)) 204 }) 205 206 t.Run("invalid fleet autoscaler", func(t *testing.T) { 207 _, m := newFakeController() 208 ext := newFakeExtensions() 209 fas, _ := defaultFixtures() 210 // this make it invalid 211 fas.Spec.Policy.Buffer = nil 212 213 _, cancel := agtesting.StartInformers(m) 214 defer cancel() 215 216 review, err := newAdmissionReview(*fas) 217 assert.Nil(t, err) 218 219 result, err := ext.validationHandler(review) 220 assert.Nil(t, err) 221 assert.False(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response)) 222 assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status) 223 assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason) 224 assert.NotEmpty(t, result.Response.Result.Details) 225 }) 226 227 t.Run("unable to unmarshal AdmissionRequest", func(t *testing.T) { 228 ext := newFakeExtensions() 229 230 review, err := newInvalidAdmissionReview() 231 assert.Nil(t, err) 232 233 _, err = ext.validationHandler(review) 234 235 if assert.NotNil(t, err) { 236 assert.Equal(t, "error unmarshalling FleetAutoscaler json after schema validation: \"MQ==\": json: cannot unmarshal string into Go value of type v1.FleetAutoscaler", err.Error()) 237 } 238 }) 239 } 240 241 func TestWebhookControllerCreationValidationHandler(t *testing.T) { 242 t.Parallel() 243 244 t.Run("valid fleet autoscaler", func(t *testing.T) { 245 _, m := newFakeController() 246 ext := newFakeExtensions() 247 fas, _ := defaultWebhookFixtures() 248 _, cancel := agtesting.StartInformers(m) 249 defer cancel() 250 251 review, err := newAdmissionReview(*fas) 252 assert.Nil(t, err) 253 254 result, err := ext.validationHandler(review) 255 assert.Nil(t, err) 256 assert.True(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response)) 257 }) 258 259 t.Run("invalid fleet autoscaler", func(t *testing.T) { 260 _, m := newFakeController() 261 ext := newFakeExtensions() 262 fas, _ := defaultWebhookFixtures() 263 // this make it invalid 264 fas.Spec.Policy.Webhook = nil 265 266 _, cancel := agtesting.StartInformers(m) 267 defer cancel() 268 269 review, err := newAdmissionReview(*fas) 270 assert.Nil(t, err) 271 272 result, err := ext.validationHandler(review) 273 assert.Nil(t, err) 274 assert.False(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response)) 275 assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status) 276 assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason) 277 assert.NotEmpty(t, result.Response.Result.Details) 278 }) 279 } 280 281 // nolint:dupl 282 func TestControllerSyncFleetAutoscaler(t *testing.T) { 283 284 t.Run("no scaling up because fleet is marked for deletion, buffer policy", func(t *testing.T) { 285 t.Parallel() 286 c, m := newFakeController() 287 fas, f := defaultFixtures() 288 fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(7) 289 290 f.Spec.Replicas = 5 291 f.Status.Replicas = 5 292 f.Status.AllocatedReplicas = 5 293 f.Status.ReadyReplicas = 0 294 f.DeletionTimestamp = &metav1.Time{ 295 Time: time.Now(), 296 } 297 298 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 299 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 300 }) 301 302 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 303 assert.FailNow(t, "fleetautoscaler should not update") 304 return false, nil, nil 305 }) 306 307 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 308 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 309 }) 310 311 m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 312 assert.FailNow(t, "fleet should not update") 313 return false, nil, nil 314 }) 315 316 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 317 defer cancel() 318 fleetAutoscalerThreadEventually(t, c, fas) 319 320 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 321 assert.Nil(t, err) 322 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 323 }) 324 325 t.Run("scaling up, buffer policy", func(t *testing.T) { 326 t.Parallel() 327 c, m := newFakeController() 328 fas, f := defaultFixtures() 329 fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(7) 330 331 f.Spec.Replicas = 5 332 f.Status.Replicas = 5 333 f.Status.AllocatedReplicas = 5 334 f.Status.ReadyReplicas = 0 335 336 fUpdated := false 337 fasUpdated := false 338 339 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 340 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 341 }) 342 343 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 344 fasUpdated = true 345 ca := action.(k8stesting.UpdateAction) 346 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 347 assert.Equal(t, fas.Status.AbleToScale, true) 348 assert.Equal(t, fas.Status.ScalingLimited, false) 349 assert.Equal(t, fas.Status.CurrentReplicas, int32(5)) 350 assert.Equal(t, fas.Status.DesiredReplicas, int32(12)) 351 assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.BufferPolicyType) 352 assert.NotNil(t, fas.Status.LastScaleTime) 353 return true, fas, nil 354 }) 355 356 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 357 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 358 }) 359 360 m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { 361 fUpdated = true 362 ca := action.(k8stesting.UpdateAction) 363 f := ca.GetObject().(*agonesv1.Fleet) 364 assert.Equal(t, f.Spec.Replicas, int32(12)) 365 return true, f, nil 366 }) 367 368 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 369 defer cancel() 370 fleetAutoscalerThreadEventually(t, c, fas) 371 372 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 373 assert.Nil(t, err) 374 assert.True(t, fUpdated, "fleet should have been updated") 375 assert.True(t, fasUpdated, "fleetautoscaler should have been updated") 376 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleet") 377 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 378 }) 379 380 t.Run("scaling up, webhook policy", func(t *testing.T) { 381 t.Parallel() 382 c, m := newFakeController() 383 fas, f := defaultWebhookFixtures() 384 f.Spec.Replicas = 50 385 f.Status.Replicas = f.Spec.Replicas 386 f.Status.AllocatedReplicas = 45 387 f.Status.ReadyReplicas = 0 388 389 ts := testServer{} 390 server := httptest.NewServer(ts) 391 defer server.Close() 392 393 fas.Spec.Policy.Webhook.URL = &(server.URL) 394 fas.Spec.Policy.Webhook.Service = nil 395 396 fUpdated := false 397 fasUpdated := false 398 399 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 400 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 401 }) 402 403 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 404 fasUpdated = true 405 ca := action.(k8stesting.UpdateAction) 406 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 407 assert.Equal(t, fas.Status.AbleToScale, true) 408 assert.Equal(t, fas.Status.ScalingLimited, false) 409 assert.Equal(t, fas.Status.CurrentReplicas, int32(50)) 410 assert.Equal(t, fas.Status.DesiredReplicas, int32(100)) 411 assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.WebhookPolicyType) 412 assert.NotNil(t, fas.Status.LastScaleTime) 413 return true, fas, nil 414 }) 415 416 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 417 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 418 }) 419 420 m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { 421 fUpdated = true 422 ca := action.(k8stesting.UpdateAction) 423 f := ca.GetObject().(*agonesv1.Fleet) 424 assert.Equal(t, f.Spec.Replicas, int32(100)) 425 return true, f, nil 426 }) 427 428 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 429 defer cancel() 430 fleetAutoscalerThreadEventually(t, c, fas) 431 432 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 433 assert.Nil(t, err) 434 assert.True(t, fUpdated, "fleet should have been updated") 435 assert.True(t, fasUpdated, "fleetautoscaler should have been updated") 436 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleet") 437 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 438 }) 439 440 t.Run("no scaling up because fleet is marked for deletion, webhook policy", func(t *testing.T) { 441 t.Parallel() 442 c, m := newFakeController() 443 fas, f := defaultWebhookFixtures() 444 f.Spec.Replicas = 50 445 f.Status.Replicas = f.Spec.Replicas 446 f.Status.AllocatedReplicas = 45 447 f.Status.ReadyReplicas = 0 448 f.DeletionTimestamp = &metav1.Time{ 449 Time: time.Now(), 450 } 451 452 ts := testServer{} 453 server := httptest.NewServer(ts) 454 defer server.Close() 455 456 fas.Spec.Policy.Webhook.URL = &(server.URL) 457 fas.Spec.Policy.Webhook.Service = nil 458 459 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 460 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 461 }) 462 463 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 464 assert.FailNow(t, "fleetautoscaler should not update") 465 return false, nil, nil 466 }) 467 468 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 469 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 470 }) 471 472 m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 473 assert.FailNow(t, "fleet should not update") 474 return false, nil, nil 475 }) 476 477 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 478 defer cancel() 479 fleetAutoscalerThreadEventually(t, c, fas) 480 481 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 482 assert.Nil(t, err) 483 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 484 }) 485 486 t.Run("scaling down, buffer policy", func(t *testing.T) { 487 t.Parallel() 488 c, m := newFakeController() 489 fas, f := defaultFixtures() 490 fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(8) 491 492 f.Spec.Replicas = 20 493 f.Status.Replicas = 20 494 f.Status.AllocatedReplicas = 5 495 f.Status.ReadyReplicas = 15 496 497 fUpdated := false 498 fasUpdated := false 499 500 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 501 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 502 }) 503 504 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 505 fasUpdated = true 506 ca := action.(k8stesting.UpdateAction) 507 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 508 assert.Equal(t, fas.Status.AbleToScale, true) 509 assert.Equal(t, fas.Status.ScalingLimited, false) 510 assert.Equal(t, fas.Status.CurrentReplicas, int32(20)) 511 assert.Equal(t, fas.Status.DesiredReplicas, int32(13)) 512 assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.BufferPolicyType) 513 assert.NotNil(t, fas.Status.LastScaleTime) 514 return true, fas, nil 515 }) 516 517 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 518 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 519 }) 520 521 m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { 522 fUpdated = true 523 ca := action.(k8stesting.UpdateAction) 524 f := ca.GetObject().(*agonesv1.Fleet) 525 assert.Equal(t, f.Spec.Replicas, int32(13)) 526 527 return true, f, nil 528 }) 529 530 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 531 defer cancel() 532 fleetAutoscalerThreadEventually(t, c, fas) 533 534 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 535 assert.Nil(t, err) 536 assert.True(t, fUpdated, "fleet should have been updated") 537 assert.True(t, fasUpdated, "fleetautoscaler should have been updated") 538 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleet") 539 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 540 }) 541 542 t.Run("no scaling down because fleet is marked for deletion, buffer policy", func(t *testing.T) { 543 t.Parallel() 544 c, m := newFakeController() 545 fas, f := defaultFixtures() 546 fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(8) 547 548 f.Spec.Replicas = 20 549 f.Status.Replicas = 20 550 f.Status.AllocatedReplicas = 5 551 f.Status.ReadyReplicas = 15 552 f.DeletionTimestamp = &metav1.Time{ 553 Time: time.Now(), 554 } 555 556 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 557 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 558 }) 559 560 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 561 assert.FailNow(t, "fleetautoscaler should not update") 562 return true, nil, nil 563 }) 564 565 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 566 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 567 }) 568 569 m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 570 assert.FailNow(t, "fleet should not update") 571 572 return false, nil, nil 573 }) 574 575 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 576 defer cancel() 577 fleetAutoscalerThreadEventually(t, c, fas) 578 579 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 580 assert.Nil(t, err) 581 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 582 }) 583 584 t.Run("no scaling no update", func(t *testing.T) { 585 t.Parallel() 586 c, m := newFakeController() 587 fas, f := defaultFixtures() 588 589 f.Spec.Replicas = 10 590 f.Status.Replicas = 10 591 f.Status.ReadyReplicas = 5 592 fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(5) 593 fas.Status.CurrentReplicas = 10 594 fas.Status.DesiredReplicas = 10 595 596 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 597 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 598 }) 599 600 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 601 assert.FailNow(t, "fleetautoscaler should not update") 602 return false, nil, nil 603 }) 604 605 m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 606 assert.FailNow(t, "fleet should not update") 607 return false, nil, nil 608 }) 609 610 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 611 defer cancel() 612 fleetAutoscalerThreadEventually(t, c, fas) 613 614 err := c.syncFleetAutoscaler(ctx, fas.ObjectMeta.Name) 615 assert.Nil(t, err) 616 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 617 }) 618 619 t.Run("fleet not available", func(t *testing.T) { 620 t.Parallel() 621 c, m := newFakeController() 622 fas, _ := defaultFixtures() 623 fas.Status.DesiredReplicas = 10 624 fas.Status.CurrentReplicas = 5 625 updated := false 626 627 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 628 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 629 }) 630 631 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 632 updated = true 633 ca := action.(k8stesting.UpdateAction) 634 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 635 assert.Equal(t, fas.Status.CurrentReplicas, int32(0)) 636 assert.Equal(t, fas.Status.DesiredReplicas, int32(0)) 637 return true, fas, nil 638 }) 639 640 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 641 defer cancel() 642 fleetAutoscalerThreadEventually(t, c, fas) 643 644 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 645 assert.Nil(t, err) 646 assert.True(t, updated) 647 648 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "FailedGetFleet") 649 }) 650 651 t.Run("fleet not available, error on status update", func(t *testing.T) { 652 t.Parallel() 653 c, m := newFakeController() 654 fas, _ := defaultFixtures() 655 fas.Status.DesiredReplicas = 10 656 fas.Status.CurrentReplicas = 5 657 658 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 659 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 660 }) 661 662 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 663 ca := action.(k8stesting.UpdateAction) 664 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 665 return true, fas, errors.New("random-err") 666 }) 667 668 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 669 defer cancel() 670 fleetAutoscalerThreadEventually(t, c, fas) 671 672 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 673 if assert.NotNil(t, err) { 674 assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error()) 675 } 676 677 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "FailedGetFleet") 678 }) 679 680 t.Run("wrong policy", func(t *testing.T) { 681 t.Parallel() 682 c, m := newFakeController() 683 fas, f := defaultFixtures() 684 685 // wrong policy, should fail 686 fas.Spec.Policy = autoscalingv1.FleetAutoscalerPolicy{ 687 Type: "WRONG TYPE", 688 } 689 690 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 691 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 692 }) 693 694 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 695 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 696 }) 697 698 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 699 defer cancel() 700 fleetAutoscalerThreadEventually(t, c, fas) 701 702 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 703 if assert.NotNil(t, err) { 704 assert.Equal(t, "error calculating autoscaling fleet: fleet-1: wrong policy type, should be one of: Buffer, Webhook, Counter, List, Schedule, Chain", err.Error()) 705 } 706 }) 707 708 t.Run("wrong policy, error on status update", func(t *testing.T) { 709 t.Parallel() 710 c, m := newFakeController() 711 fas, f := defaultFixtures() 712 fas.Status.DesiredReplicas = 10 713 // wrong policy, should fail 714 fas.Spec.Policy = autoscalingv1.FleetAutoscalerPolicy{ 715 Type: "WRONG TYPE", 716 } 717 718 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 719 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 720 }) 721 722 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 723 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 724 }) 725 726 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 727 ca := action.(k8stesting.UpdateAction) 728 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 729 return true, fas, errors.New("random-err") 730 }) 731 732 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 733 defer cancel() 734 fleetAutoscalerThreadEventually(t, c, fas) 735 736 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 737 if assert.NotNil(t, err) { 738 assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error()) 739 } 740 }) 741 742 t.Run("error on scale fleet step", func(t *testing.T) { 743 t.Parallel() 744 c, m := newFakeController() 745 fas, f := defaultFixtures() 746 747 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 748 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 749 }) 750 751 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 752 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 753 }) 754 755 m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { 756 ca := action.(k8stesting.UpdateAction) 757 return true, ca.GetObject().(*agonesv1.Fleet), errors.New("random-err") 758 }) 759 760 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 761 defer cancel() 762 fleetAutoscalerThreadEventually(t, c, fas) 763 764 err := c.syncFleetAutoscaler(ctx, "default/fas-1") 765 if assert.NotNil(t, err) { 766 assert.Equal(t, "error autoscaling fleet fleet-1 to 7 replicas: error updating replicas for fleet fleet-1: random-err", err.Error()) 767 } 768 769 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleetError") 770 }) 771 772 t.Run("Missing fleet autoscaler, doesn't fail/panic", func(t *testing.T) { 773 t.Parallel() 774 775 c, m := newFakeController() 776 m.AgonesClient.AddReactor("get", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 777 ga := action.(k8stesting.GetAction) 778 return true, nil, k8serrors.NewNotFound(corev1.Resource("gameserver"), ga.GetName()) 779 }) 780 781 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.fleetAutoscalerSynced) 782 defer cancel() 783 784 require.NoError(t, c.syncFleetAutoscaler(ctx, "default/fas-1")) 785 }) 786 } 787 788 // fleetAutoscalerThreadEventually waits to see if a thread is started for the given fleet autoscaler 789 // this is a sign that the informer has been started and the fleet autoscaler has been processed 790 // by the informer. 791 func fleetAutoscalerThreadEventually(t *testing.T, c *Controller, fas *autoscalingv1.FleetAutoscaler) { 792 require.Eventually(t, func() bool { 793 c.fasThreadMutex.Lock() 794 defer c.fasThreadMutex.Unlock() 795 _, ok := c.fasThreads[fas.ObjectMeta.UID] 796 return ok 797 }, 5*time.Second, 100*time.Millisecond, "fleet autoscaler controller didn't start the sync thread") 798 } 799 800 func TestControllerScaleFleet(t *testing.T) { 801 t.Parallel() 802 803 t.Run("fleet that must be scaled", func(t *testing.T) { 804 c, m := newFakeController() 805 fas, f := defaultFixtures() 806 replicas := f.Spec.Replicas + 5 807 808 update := false 809 810 m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { 811 update = true 812 ca := action.(k8stesting.UpdateAction) 813 f := ca.GetObject().(*agonesv1.Fleet) 814 assert.Equal(t, replicas, f.Spec.Replicas) 815 816 return true, f, nil 817 }) 818 819 err := c.scaleFleet(context.Background(), fas, f, replicas) 820 assert.Nil(t, err) 821 assert.True(t, update, "Fleet should be updated") 822 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingFleet") 823 }) 824 825 t.Run("error on updating fleet", func(t *testing.T) { 826 c, m := newFakeController() 827 fas, f := defaultFixtures() 828 replicas := f.Spec.Replicas + 5 829 830 m.AgonesClient.AddReactor("update", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { 831 ca := action.(k8stesting.UpdateAction) 832 return true, ca.GetObject().(*agonesv1.Fleet), errors.New("random-err") 833 }) 834 835 err := c.scaleFleet(context.Background(), fas, f, replicas) 836 if assert.NotNil(t, err) { 837 assert.Equal(t, "error updating replicas for fleet fleet-1: random-err", err.Error()) 838 } 839 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "AutoScalingFleetError") 840 }) 841 842 t.Run("equal replicas, no update", func(t *testing.T) { 843 c, m := newFakeController() 844 fas, f := defaultFixtures() 845 replicas := f.Spec.Replicas 846 847 m.AgonesClient.AddReactor("update", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 848 assert.FailNow(t, "fleet should not update") 849 return false, nil, nil 850 }) 851 852 err := c.scaleFleet(context.Background(), fas, f, replicas) 853 assert.Nil(t, err) 854 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 855 }) 856 } 857 858 func TestControllerUpdateStatus(t *testing.T) { 859 t.Parallel() 860 861 t.Run("must update", func(t *testing.T) { 862 c, m := newFakeController() 863 fas, _ := defaultFixtures() 864 865 fasUpdated := false 866 867 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 868 fasUpdated = true 869 ca := action.(k8stesting.UpdateAction) 870 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 871 assert.Equal(t, fas.Status.AbleToScale, true) 872 assert.Equal(t, fas.Status.ScalingLimited, false) 873 assert.Equal(t, fas.Status.CurrentReplicas, int32(10)) 874 assert.Equal(t, fas.Status.DesiredReplicas, int32(20)) 875 assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.BufferPolicyType) 876 assert.NotNil(t, fas.Status.LastScaleTime) 877 return true, fas, nil 878 }) 879 880 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 881 defer cancel() 882 883 err := c.updateStatus(ctx, fas, 10, 20, true, false, fas.Spec.Policy.Type) 884 assert.Nil(t, err) 885 assert.True(t, fasUpdated) 886 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 887 }) 888 889 t.Run("must not update", func(t *testing.T) { 890 c, m := newFakeController() 891 fas, _ := defaultFixtures() 892 893 fas.Status.AbleToScale = true 894 fas.Status.ScalingLimited = false 895 fas.Status.CurrentReplicas = 10 896 fas.Status.DesiredReplicas = 20 897 fas.Status.LastScaleTime = nil 898 fas.Status.LastAppliedPolicy = fas.Spec.Policy.Type 899 900 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 901 assert.FailNow(t, "should not update") 902 return false, nil, nil 903 }) 904 905 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 906 defer cancel() 907 908 err := c.updateStatus(ctx, fas, fas.Status.CurrentReplicas, fas.Status.DesiredReplicas, false, fas.Status.ScalingLimited, fas.Spec.Policy.Type) 909 assert.Nil(t, err) 910 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 911 }) 912 913 t.Run("update with error", func(t *testing.T) { 914 c, m := newFakeController() 915 fas, _ := defaultFixtures() 916 917 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 918 return true, nil, errors.New("random-err") 919 }) 920 921 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 922 defer cancel() 923 924 err := c.updateStatus(ctx, fas, fas.Status.CurrentReplicas, fas.Status.DesiredReplicas, false, fas.Status.ScalingLimited, fas.Spec.Policy.Type) 925 if assert.NotNil(t, err) { 926 assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error()) 927 } 928 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 929 }) 930 931 t.Run("update with a scaling limit", func(t *testing.T) { 932 c, m := newFakeController() 933 fas, _ := defaultFixtures() 934 935 err := c.updateStatus(context.Background(), fas, 10, 20, true, true, fas.Spec.Policy.Type) 936 assert.Nil(t, err) 937 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingLimited") 938 }) 939 940 t.Run("update with a scaling limit with minimum", func(t *testing.T) { 941 c, m := newFakeController() 942 fas, _ := defaultFixtures() 943 944 err := c.updateStatus(context.Background(), fas, 1, 3, true, true, fas.Spec.Policy.Type) 945 assert.Nil(t, err) 946 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "limited to minimum size of 3") 947 }) 948 949 t.Run("update with a scaling limit with maximum", func(t *testing.T) { 950 c, m := newFakeController() 951 fas, _ := defaultFixtures() 952 953 err := c.updateStatus(context.Background(), fas, 12, 10, true, true, fas.Spec.Policy.Type) 954 assert.Nil(t, err) 955 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "limited to maximum size of 10") 956 }) 957 } 958 959 func TestControllerUpdateStatusUnableToScale(t *testing.T) { 960 t.Parallel() 961 962 t.Run("must update", func(t *testing.T) { 963 c, m := newFakeController() 964 fas, _ := defaultFixtures() 965 fas.Status.DesiredReplicas = 10 966 967 fasUpdated := false 968 969 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 970 fasUpdated = true 971 ca := action.(k8stesting.UpdateAction) 972 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 973 assert.Equal(t, fas.Status.AbleToScale, false) 974 assert.Equal(t, fas.Status.ScalingLimited, false) 975 assert.Equal(t, fas.Status.CurrentReplicas, int32(0)) 976 assert.Equal(t, fas.Status.DesiredReplicas, int32(0)) 977 assert.Equal(t, fas.Status.LastAppliedPolicy, autoscalingv1.FleetAutoscalerPolicyType("")) 978 assert.Nil(t, fas.Status.LastScaleTime) 979 return true, fas, nil 980 }) 981 982 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 983 defer cancel() 984 985 err := c.updateStatusUnableToScale(ctx, fas) 986 assert.Nil(t, err) 987 assert.True(t, fasUpdated) 988 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 989 }) 990 991 t.Run("update with error", func(t *testing.T) { 992 c, m := newFakeController() 993 fas, _ := defaultFixtures() 994 fas.Status.DesiredReplicas = 10 995 996 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(action k8stesting.Action) (bool, runtime.Object, error) { 997 ca := action.(k8stesting.UpdateAction) 998 fas := ca.GetObject().(*autoscalingv1.FleetAutoscaler) 999 return true, fas, errors.New("random-err") 1000 }) 1001 1002 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 1003 defer cancel() 1004 1005 err := c.updateStatusUnableToScale(ctx, fas) 1006 if assert.NotNil(t, err) { 1007 assert.Equal(t, "error updating status for fleetautoscaler fas-1: random-err", err.Error()) 1008 } 1009 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 1010 }) 1011 1012 t.Run("must not update", func(t *testing.T) { 1013 c, m := newFakeController() 1014 fas, _ := defaultFixtures() 1015 fas.Status.AbleToScale = false 1016 fas.Status.ScalingLimited = false 1017 fas.Status.CurrentReplicas = 0 1018 fas.Status.DesiredReplicas = 0 1019 fas.Status.LastAppliedPolicy = autoscalingv1.FleetAutoscalerPolicyType("") 1020 1021 m.AgonesClient.AddReactor("update", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1022 assert.FailNow(t, "fleetautoscaler should not update") 1023 return false, nil, nil 1024 }) 1025 1026 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 1027 defer cancel() 1028 1029 err := c.updateStatusUnableToScale(ctx, fas) 1030 assert.Nil(t, err) 1031 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 1032 }) 1033 } 1034 1035 func TestControllerEvents(t *testing.T) { 1036 t.Parallel() 1037 1038 c, mocks := newFakeController() 1039 fakeWatch := watch.NewFake() 1040 mocks.AgonesClient.AddWatchReactor("fleetautoscalers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 1041 _, cancel := agtesting.StartInformers(mocks, c.fleetAutoscalerSynced) 1042 defer cancel() 1043 1044 // add fleet autoscaler 1045 fas, _ := defaultFixtures() 1046 fakeWatch.Add(fas.DeepCopy()) 1047 1048 require.Eventually(t, func() bool { 1049 c.fasThreadMutex.Lock() 1050 defer c.fasThreadMutex.Unlock() 1051 return len(c.fasThreads) == 1 1052 }, 30*time.Second, time.Second, "should be added") 1053 1054 c.fasThreadMutex.Lock() 1055 require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation) 1056 c.fasThreadMutex.Unlock() 1057 1058 // modify the fleet autoscaler 1059 fas.ObjectMeta.Generation++ 1060 fakeWatch.Modify(fas.DeepCopy()) 1061 1062 require.Eventually(t, func() bool { 1063 c.fasThreadMutex.Lock() 1064 defer c.fasThreadMutex.Unlock() 1065 return fas.ObjectMeta.Generation == c.fasThreads[fas.ObjectMeta.UID].generation 1066 }, 30*time.Second, time.Second, "should be updated") 1067 1068 // delete the fleet auto scaler 1069 fakeWatch.Delete(fas.DeepCopy()) 1070 require.Eventually(t, func() bool { 1071 c.fasThreadMutex.Lock() 1072 defer c.fasThreadMutex.Unlock() 1073 return len(c.fasThreads) == 0 1074 }, 30*time.Second, time.Second, "should be deleted") 1075 } 1076 1077 func TestControllerAddUpdateDeleteFasThread(t *testing.T) { 1078 t.Parallel() 1079 1080 var counter int64 1081 c, m := newFakeController() 1082 c.workerqueue.SyncHandler = func(_ context.Context, _ string) error { 1083 atomic.AddInt64(&counter, 1) 1084 return nil 1085 } 1086 1087 m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1088 return true, agtesting.NewEstablishedCRD(), nil 1089 }) 1090 1091 ctx, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 1092 defer cancel() 1093 go func() { 1094 require.NoError(t, c.Run(ctx, 1)) 1095 }() 1096 1097 fas, _ := defaultFixtures() 1098 fas.Spec.Sync.FixedInterval.Seconds = 1 1099 1100 c.addFasThread(fas, true) 1101 1102 // unfortunately we can't mock the timer, so we'll confirm that two enqueue processes fire. One on method execution, 1103 // and then one based on the ticker. 1104 require.Eventuallyf(t, func() bool { 1105 return atomic.LoadInt64(&counter) >= 2 1106 }, 10*time.Second, time.Second, "Should have at least two counters") 1107 1108 c.fasThreadMutex.Lock() 1109 require.Len(t, c.fasThreads, 1) 1110 c.fasThreadMutex.Unlock() 1111 1112 // update with the same values 1113 c.updateFasThread(ctx, fas) 1114 c.fasThreadMutex.Lock() 1115 require.Len(t, c.fasThreads, 1) 1116 require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation) 1117 c.fasThreadMutex.Unlock() 1118 1119 // update duration 1120 fas.Spec.Sync.FixedInterval.Seconds = 3 1121 fas.ObjectMeta.Generation++ 1122 1123 c.updateFasThread(ctx, fas) 1124 c.fasThreadMutex.Lock() 1125 require.Len(t, c.fasThreads, 1) 1126 require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation) 1127 c.fasThreadMutex.Unlock() 1128 1129 // update, but with a second fas, that doesn't exist in the system yet 1130 fas2 := fas.DeepCopy() 1131 fas2.Spec.Sync.FixedInterval.Seconds = 1 1132 fas2.ObjectMeta.Generation = 5 1133 fas2.ObjectMeta.UID = "4321" 1134 1135 c.updateFasThread(ctx, fas2) 1136 c.fasThreadMutex.Lock() 1137 require.Len(t, c.fasThreads, 2) 1138 require.Equal(t, fas.ObjectMeta.Generation, c.fasThreads[fas.ObjectMeta.UID].generation) 1139 require.Equal(t, fas2.ObjectMeta.Generation, c.fasThreads[fas2.ObjectMeta.UID].generation) 1140 c.fasThreadMutex.Unlock() 1141 1142 // delete the current fas. 1143 c.deleteFasThread(ctx, fas, true) 1144 c.fasThreadMutex.Lock() 1145 require.Len(t, c.fasThreads, 1) 1146 require.Equal(t, fas2.ObjectMeta.Generation, c.fasThreads[fas2.ObjectMeta.UID].generation) 1147 c.fasThreadMutex.Unlock() 1148 1149 c.deleteFasThread(ctx, fas2, true) 1150 c.fasThreadMutex.Lock() 1151 require.Len(t, c.fasThreads, 0) 1152 c.fasThreadMutex.Unlock() 1153 1154 // we shouldn't get any more updates, so wait for 3 checks in a row that have the 1155 // same counter amount to prove that there aren't any changes for a while. 1156 var check []int64 1157 require.Eventually(t, func() bool { 1158 check = append(check, atomic.LoadInt64(&counter)) 1159 l := len(check) 1160 if l < 3 { 1161 return false 1162 } 1163 l-- 1164 return check[l] == check[l-1] && check[l-1] == check[l-2] 1165 }, 30*time.Second, 2*time.Second, "changes keep happening", check) 1166 } 1167 1168 func TestControllerCleanFasThreads(t *testing.T) { 1169 c, m := newFakeController() 1170 fas, _ := defaultFixtures() 1171 1172 m.AgonesClient.AddReactor("list", "fleetautoscalers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1173 return true, &autoscalingv1.FleetAutoscalerList{Items: []autoscalingv1.FleetAutoscaler{*fas}}, nil 1174 }) 1175 1176 _, cancel := agtesting.StartInformers(m, c.fleetAutoscalerSynced) 1177 defer cancel() 1178 1179 c.fasThreadMutex.Lock() 1180 c.fasThreads = map[types.UID]fasThread{ 1181 "1": {func() {}, map[string]any{}, 1}, 1182 "2": {func() {}, map[string]any{}, 2}, 1183 fas.ObjectMeta.UID: {func() {}, map[string]any{}, 3}, 1184 } 1185 c.fasThreadMutex.Unlock() 1186 1187 key, err := cache.MetaNamespaceKeyFunc(fas) 1188 require.NoError(t, err) 1189 require.NoError(t, c.cleanFasThreads(context.Background(), key)) 1190 1191 require.Len(t, c.fasThreads, 1) 1192 require.Equal(t, int64(3), c.fasThreads[fas.ObjectMeta.UID].generation) 1193 } 1194 1195 func defaultFixtures() (*autoscalingv1.FleetAutoscaler, *agonesv1.Fleet) { 1196 f := &agonesv1.Fleet{ 1197 ObjectMeta: metav1.ObjectMeta{ 1198 Name: "fleet-1", 1199 Namespace: "default", 1200 UID: "1234", 1201 }, 1202 Spec: agonesv1.FleetSpec{ 1203 Replicas: 8, 1204 Scheduling: apis.Packed, 1205 Template: agonesv1.GameServerTemplateSpec{}, 1206 }, 1207 Status: agonesv1.FleetStatus{ 1208 Replicas: 5, 1209 ReadyReplicas: 3, 1210 ReservedReplicas: 3, 1211 AllocatedReplicas: 2, 1212 }, 1213 } 1214 1215 fas := &autoscalingv1.FleetAutoscaler{ 1216 ObjectMeta: metav1.ObjectMeta{ 1217 Name: "fas-1", 1218 Namespace: "default", 1219 Generation: 2, 1220 UID: "4567", 1221 }, 1222 Spec: autoscalingv1.FleetAutoscalerSpec{ 1223 FleetName: f.ObjectMeta.Name, 1224 Policy: autoscalingv1.FleetAutoscalerPolicy{ 1225 Type: autoscalingv1.BufferPolicyType, 1226 Buffer: &autoscalingv1.BufferPolicy{ 1227 BufferSize: intstr.FromInt(5), 1228 MaxReplicas: 100, 1229 }, 1230 }, 1231 Sync: &autoscalingv1.FleetAutoscalerSync{ 1232 Type: autoscalingv1.FixedIntervalSyncType, 1233 FixedInterval: autoscalingv1.FixedIntervalSync{ 1234 Seconds: 30, 1235 }, 1236 }, 1237 }, 1238 } 1239 1240 return fas, f 1241 } 1242 1243 func defaultWebhookFixtures() (*autoscalingv1.FleetAutoscaler, *agonesv1.Fleet) { 1244 fas, f := defaultFixtures() 1245 fas.Spec.Policy.Type = autoscalingv1.WebhookPolicyType 1246 fas.Spec.Policy.Buffer = nil 1247 url := "/autoscaler" 1248 fas.Spec.Policy.Webhook = &autoscalingv1.URLConfiguration{ 1249 Service: &admregv1.ServiceReference{ 1250 Name: "fleetautoscaler-service", 1251 Path: &url, 1252 }, 1253 } 1254 1255 return fas, f 1256 } 1257 1258 // newFakeController returns a controller, backed by the fake Clientset 1259 func newFakeController() (*Controller, agtesting.Mocks) { 1260 m := agtesting.NewMocks() 1261 counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory) 1262 c := NewController(healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory, counter) 1263 c.recorder = m.FakeRecorder 1264 return c, m 1265 } 1266 1267 // newFakeExtensions returns a fake extensions struct 1268 func newFakeExtensions() *Extensions { 1269 return NewExtensions(webhooks.NewWebHook(http.NewServeMux())) 1270 } 1271 1272 func newAdmissionReview(fas autoscalingv1.FleetAutoscaler) (admissionv1.AdmissionReview, error) { 1273 raw, err := json.Marshal(fas) 1274 if err != nil { 1275 return admissionv1.AdmissionReview{}, err 1276 } 1277 review := admissionv1.AdmissionReview{ 1278 Request: &admissionv1.AdmissionRequest{ 1279 Kind: gvk, 1280 Operation: admissionv1.Create, 1281 Object: runtime.RawExtension{ 1282 Raw: raw, 1283 }, 1284 Namespace: "default", 1285 }, 1286 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1287 } 1288 return review, err 1289 } 1290 1291 func newInvalidAdmissionReview() (admissionv1.AdmissionReview, error) { 1292 raw, err := json.Marshal([]byte(`1`)) 1293 if err != nil { 1294 return admissionv1.AdmissionReview{}, err 1295 } 1296 review := admissionv1.AdmissionReview{ 1297 Request: &admissionv1.AdmissionRequest{ 1298 Kind: gvk, 1299 Operation: admissionv1.Create, 1300 Object: runtime.RawExtension{ 1301 Raw: raw, 1302 }, 1303 Namespace: "default", 1304 }, 1305 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1306 } 1307 return review, nil 1308 }