agones.dev/agones@v1.53.0/pkg/fleets/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 // nolint:goconst 16 package fleets 17 18 import ( 19 "context" 20 "encoding/json" 21 "net/http" 22 "testing" 23 "time" 24 25 "agones.dev/agones/pkg/apis" 26 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 27 v1 "agones.dev/agones/pkg/apis/agones/v1" 28 applyconfigurations "agones.dev/agones/pkg/client/applyconfiguration/agones/v1" 29 agonesv1clientset "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1" 30 agonesv1client "agones.dev/agones/pkg/client/listers/agones/v1" 31 "agones.dev/agones/pkg/cloudproduct/generic" 32 agtesting "agones.dev/agones/pkg/testing" 33 utilruntime "agones.dev/agones/pkg/util/runtime" 34 "agones.dev/agones/pkg/util/webhooks" 35 "github.com/heptiolabs/healthcheck" 36 "github.com/pkg/errors" 37 "github.com/sirupsen/logrus" 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40 "gomodules.xyz/jsonpatch/v2" 41 admissionv1 "k8s.io/api/admission/v1" 42 appsv1 "k8s.io/api/apps/v1" 43 autoscalingv1 "k8s.io/api/autoscaling/v1" 44 corev1 "k8s.io/api/core/v1" 45 "k8s.io/apimachinery/pkg/api/resource" 46 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 47 "k8s.io/apimachinery/pkg/labels" 48 "k8s.io/apimachinery/pkg/runtime" 49 "k8s.io/apimachinery/pkg/types" 50 "k8s.io/apimachinery/pkg/util/intstr" 51 "k8s.io/apimachinery/pkg/watch" 52 k8stesting "k8s.io/client-go/testing" 53 "k8s.io/client-go/tools/cache" 54 ) 55 56 func TestControllerSyncFleet(t *testing.T) { 57 t.Parallel() 58 59 t.Run("no gameserverset, create it", func(t *testing.T) { 60 f := defaultFixture() 61 c, m := newFakeController() 62 63 created := false 64 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 65 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 66 }) 67 68 m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 69 ca := action.(k8stesting.CreateAction) 70 gsSet := ca.GetObject().(*agonesv1.GameServerSet) 71 72 created = true 73 assert.True(t, metav1.IsControlledBy(gsSet, f)) 74 assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas) 75 76 return true, gsSet, nil 77 }) 78 79 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced) 80 defer cancel() 81 82 err := c.syncFleet(ctx, "default/fleet-1") 83 assert.Nil(t, err) 84 assert.True(t, created, "gameserverset should have been created") 85 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "CreatingGameServerSet") 86 }) 87 88 t.Run("gameserverset with the same number of replicas", func(t *testing.T) { 89 t.Parallel() 90 f := defaultFixture() 91 f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType 92 c, m := newFakeController() 93 gsSet := f.GameServerSet() 94 95 gsSet.ObjectMeta.Name = "gsSet1" 96 gsSet.ObjectMeta.UID = "4321" 97 gsSet.Spec.Replicas = f.Spec.Replicas 98 99 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 100 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 101 }) 102 103 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 104 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 105 }) 106 107 m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 108 assert.FailNow(t, "gameserverset should not be created") 109 return true, nil, nil 110 }) 111 112 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 113 assert.FailNow(t, "gameserverset should not have been updated") 114 return true, nil, nil 115 }) 116 117 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 118 defer cancel() 119 120 err := c.syncFleet(ctx, "default/fleet-1") 121 assert.Nil(t, err) 122 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 123 }) 124 125 t.Run("gameserverset with different number of replicas", func(t *testing.T) { 126 if utilruntime.FeatureEnabled(utilruntime.FeatureRollingUpdateFix) { 127 t.SkipNow() 128 } 129 130 f := defaultFixture() 131 f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType 132 c, m := newFakeController() 133 gsSet := f.GameServerSet() 134 gsSet.ObjectMeta.Name = "gsSet1" 135 gsSet.ObjectMeta.UID = "1234" 136 gsSet.Spec.Replicas = f.Spec.Replicas + 10 137 updated := false 138 139 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 140 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 141 }) 142 143 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 144 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 145 }) 146 147 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 148 updated = true 149 150 ua := action.(k8stesting.UpdateAction) 151 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 152 assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas) 153 154 return true, gsSet, nil 155 }) 156 157 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 158 defer cancel() 159 160 err := c.syncFleet(ctx, "default/fleet-1") 161 assert.Nil(t, err) 162 assert.True(t, updated, "gameserverset should have been updated") 163 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 164 }) 165 166 t.Run("gameserverset with different scheduling", func(t *testing.T) { 167 f := defaultFixture() 168 f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType 169 c, m := newFakeController() 170 gsSet := f.GameServerSet() 171 gsSet.ObjectMeta.Name = "gsSet1" 172 gsSet.ObjectMeta.UID = "1234" 173 gsSet.Spec.Replicas = f.Spec.Replicas 174 gsSet.Spec.Scheduling = apis.Distributed 175 updated := false 176 177 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 178 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 179 }) 180 181 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 182 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 183 }) 184 185 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 186 updated = true 187 188 ua := action.(k8stesting.UpdateAction) 189 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 190 assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas) 191 assert.Equal(t, f.Spec.Scheduling, gsSet.Spec.Scheduling) 192 return true, gsSet, nil 193 }) 194 195 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 196 defer cancel() 197 198 err := c.syncFleet(ctx, "default/fleet-1") 199 assert.Nil(t, err) 200 assert.True(t, updated, "gameserverset should have been updated") 201 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 202 }) 203 204 t.Run("gameserverset with different image details", func(t *testing.T) { 205 if utilruntime.FeatureEnabled(utilruntime.FeatureRollingUpdateFix) { 206 t.SkipNow() 207 } 208 209 f := defaultFixture() 210 f.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType 211 f.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 5555}} 212 f.Status.ReadyReplicas = 5 213 c, m := newFakeController() 214 gsSet := f.GameServerSet() 215 gsSet.ObjectMeta.Name = "gsSet1" 216 gsSet.ObjectMeta.UID = "4321" 217 gsSet.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 7777}} 218 gsSet.Spec.Replicas = f.Spec.Replicas 219 gsSet.Spec.Scheduling = f.Spec.Scheduling 220 gsSet.Status.Replicas = 5 221 updated := false 222 created := false 223 224 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 225 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 226 }) 227 228 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 229 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 230 }) 231 232 m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 233 created = true 234 ca := action.(k8stesting.CreateAction) 235 gsSet := ca.GetObject().(*agonesv1.GameServerSet) 236 assert.Equal(t, int32(2), gsSet.Spec.Replicas) 237 assert.Equal(t, f.Spec.Template.Spec.Ports[0].HostPort, gsSet.Spec.Template.Spec.Ports[0].HostPort) 238 239 return true, gsSet, nil 240 }) 241 242 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 243 updated = true 244 ua := action.(k8stesting.UpdateAction) 245 // separate update of status subresource 246 if ua.GetSubresource() != "" { 247 assert.Equal(t, ua.GetSubresource(), "status") 248 return true, nil, nil 249 } 250 // update main resource 251 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 252 assert.Equal(t, int32(4), gsSet.Spec.Replicas) 253 assert.Equal(t, "gsSet1", gsSet.ObjectMeta.Name) 254 255 return true, gsSet, nil 256 }) 257 258 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 259 defer cancel() 260 261 err := c.syncFleet(ctx, "default/fleet-1") 262 assert.Nil(t, err) 263 assert.True(t, updated, "gameserverset should have been updated") 264 assert.True(t, created, "gameserverset should have been created") 265 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 266 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "CreatingGameServerSet") 267 }) 268 269 t.Run("fleet marked for deletion shouldn't take any action on gameserver sets", func(t *testing.T) { 270 f := defaultFixture() 271 f.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType 272 f.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 5555}} 273 f.Status.ReadyReplicas = 5 274 f.DeletionTimestamp = &metav1.Time{ 275 Time: time.Now(), 276 } 277 278 c, m := newFakeController() 279 gsSet := f.GameServerSet() 280 gsSet.ObjectMeta.Name = "gsSet1" 281 gsSet.ObjectMeta.UID = "4321" 282 gsSet.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 7777}} 283 gsSet.Spec.Replicas = f.Spec.Replicas 284 gsSet.Spec.Scheduling = f.Spec.Scheduling 285 gsSet.Status.Replicas = 5 286 287 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 288 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 289 }) 290 291 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 292 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 293 }) 294 295 m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 296 assert.FailNow(t, "gameserverset should not have been created") 297 return false, nil, nil 298 }) 299 300 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 301 assert.FailNow(t, "gameserverset should not have been updated") 302 return false, nil, nil 303 }) 304 305 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 306 defer cancel() 307 308 err := c.syncFleet(ctx, "default/fleet-1") 309 assert.Nil(t, err) 310 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 311 }) 312 313 t.Run("error on getting fleet", func(t *testing.T) { 314 c, _ := newFakeController() 315 c.fleetLister = &fakeFleetListerWithErr{} 316 317 err := c.syncFleet(context.Background(), "default/fleet-1") 318 assert.EqualError(t, err, "error retrieving fleet fleet-1 from namespace default: err-from-namespace-lister") 319 }) 320 321 t.Run("error on getting list of GS", func(t *testing.T) { 322 f := defaultFixture() 323 c, m := newFakeController() 324 c.gameServerSetLister = &fakeGSSListerWithErr{} 325 326 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 327 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 328 }) 329 330 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 331 defer cancel() 332 333 err := c.syncFleet(ctx, "default/fleet-1") 334 assert.EqualError(t, err, "error listing gameserversets for fleet fleet-1: random-err") 335 }) 336 337 t.Run("fleet not found", func(t *testing.T) { 338 c, _ := newFakeController() 339 340 err := c.syncFleet(context.Background(), "default/fleet-1") 341 assert.Nil(t, err) 342 }) 343 344 t.Run("fleet invalid strategy type", func(t *testing.T) { 345 f := defaultFixture() 346 c, m := newFakeController() 347 f.Spec.Strategy.Type = "invalid-strategy-type" 348 349 gsSet := f.GameServerSet() 350 // make gsSet.Spec.Template and f.Spec.Template different in order to make 'rest' list not empty 351 gsSet.Spec.Template.Name = "qqqqqqqqqqqqqqqqqqq" 352 // make sure there is at least one replica, or the logic will escape before the check. 353 gsSet.Spec.Replicas = 1 354 355 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 356 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 357 }) 358 359 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 360 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 361 }) 362 363 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 364 defer cancel() 365 366 err := c.syncFleet(ctx, "default/fleet-1") 367 assert.EqualError(t, err, "unexpected deployment strategy type: invalid-strategy-type") 368 }) 369 370 t.Run("error on deleteEmptyGameServerSets", func(t *testing.T) { 371 f := defaultFixture() 372 c, m := newFakeController() 373 374 gsSet := f.GameServerSet() 375 // make gsSet.Spec.Template and f.Spec.Template different in order to make 'rest' list not empty 376 gsSet.Spec.Template.Name = "qqqqqqqqqqqqqqqqqqq" 377 378 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 379 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 380 }) 381 382 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 383 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 384 }) 385 386 m.AgonesClient.AddReactor("delete", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 387 return true, nil, errors.New("random-err") 388 }) 389 390 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 391 defer cancel() 392 393 err := c.syncFleet(ctx, "default/fleet-1") 394 assert.EqualError(t, err, "error updating gameserverset : random-err") 395 }) 396 397 t.Run("error on upsertGameServerSet", func(t *testing.T) { 398 f := defaultFixture() 399 c, m := newFakeController() 400 401 gsSet := f.GameServerSet() 402 403 m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 404 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 405 }) 406 407 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 408 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 409 }) 410 411 m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 412 return true, nil, errors.New("random-err") 413 }) 414 415 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 416 defer cancel() 417 418 err := c.syncFleet(ctx, "default/fleet-1") 419 assert.EqualError(t, err, "error creating gameserverset for fleet fleet-1: random-err") 420 }) 421 } 422 423 func TestControllerCreationValidationHandler(t *testing.T) { 424 t.Parallel() 425 426 ext := newFakeExtensions() 427 428 t.Run("invalid JSON", func(t *testing.T) { 429 raw, err := json.Marshal([]byte(`1`)) 430 require.NoError(t, err) 431 review := getAdmissionReview(raw) 432 433 _, err = ext.creationValidationHandler(review) 434 assert.EqualError(t, err, "error unmarshalling Fleet json after schema validation: \"MQ==\": json: cannot unmarshal string into Go value of type v1.Fleet") 435 }) 436 437 t.Run("invalid fleet", func(t *testing.T) { 438 fixture := agonesv1.Fleet{} 439 440 raw, err := json.Marshal(fixture) 441 require.NoError(t, err) 442 review := getAdmissionReview(raw) 443 444 result, err := ext.creationValidationHandler(review) 445 require.NoError(t, err) 446 assert.False(t, result.Response.Allowed) 447 assert.Equal(t, "Failure", result.Response.Result.Status) 448 }) 449 450 t.Run("valid fleet", func(t *testing.T) { 451 gsSpec := *defaultGSSpec() 452 f := defaultFixture() 453 f.Spec.Template = gsSpec 454 455 raw, err := json.Marshal(f) 456 require.NoError(t, err) 457 review := getAdmissionReview(raw) 458 459 result, err := ext.creationValidationHandler(review) 460 require.NoError(t, err) 461 assert.True(t, result.Response.Allowed) 462 }) 463 } 464 465 func TestControllerCreationMutationHandler(t *testing.T) { 466 t.Parallel() 467 468 t.Run("ok scenario", func(t *testing.T) { 469 ext := newFakeExtensions() 470 fixture := agonesv1.Fleet{} 471 472 raw, err := json.Marshal(fixture) 473 require.NoError(t, err) 474 review := getAdmissionReview(raw) 475 476 result, err := ext.creationMutationHandler(review) 477 require.NoError(t, err) 478 assert.True(t, result.Response.Allowed) 479 assert.Equal(t, admissionv1.PatchTypeJSONPatch, *result.Response.PatchType) 480 481 patch := &jsonpatch.ByPath{} 482 err = json.Unmarshal(result.Response.Patch, patch) 483 require.NoError(t, err) 484 485 assertContains := func(patch *jsonpatch.ByPath, op jsonpatch.JsonPatchOperation) { 486 found := false 487 for _, p := range *patch { 488 if assert.ObjectsAreEqualValues(p, op) { 489 found = true 490 } 491 } 492 493 assert.True(t, found, "Could not find operation %#v in patch %v", op, *patch) 494 } 495 496 assertContains(patch, jsonpatch.JsonPatchOperation{Operation: "add", Path: "/spec/strategy/type", Value: "RollingUpdate"}) 497 }) 498 499 t.Run("invalid JSON", func(t *testing.T) { 500 ext := newFakeExtensions() 501 raw, err := json.Marshal([]byte(`1`)) 502 require.NoError(t, err) 503 review := getAdmissionReview(raw) 504 505 result, err := ext.creationMutationHandler(review) 506 assert.NoError(t, err) 507 require.Nil(t, result.Response.PatchType) 508 }) 509 } 510 511 func TestControllerRun(t *testing.T) { 512 t.Parallel() 513 514 fleet := defaultFixture() 515 c, m := newFakeController() 516 received := make(chan string) 517 defer close(received) 518 519 m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) { 520 return true, agtesting.NewEstablishedCRD(), nil 521 }) 522 523 fleetWatch := watch.NewFake() 524 m.AgonesClient.AddWatchReactor("fleets", k8stesting.DefaultWatchReactor(fleetWatch, nil)) 525 526 gsSetWatch := watch.NewFake() 527 m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil)) 528 529 c.workerqueue.SyncHandler = func(_ context.Context, name string) error { 530 received <- name 531 return nil 532 } 533 534 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced) 535 defer cancel() 536 537 go func() { 538 err := c.Run(ctx, 1) 539 assert.Nil(t, err) 540 }() 541 542 f := func() string { 543 select { 544 case result := <-received: 545 return result 546 case <-time.After(3 * time.Second): 547 assert.FailNow(t, "timeout occurred") 548 } 549 return "" 550 } 551 552 expected, err := cache.MetaNamespaceKeyFunc(fleet) 553 require.NoError(t, err) 554 555 // test adding fleet 556 fleetWatch.Add(fleet.DeepCopy()) 557 assert.Equal(t, expected, f()) 558 559 // test updating fleet 560 fCopy := fleet.DeepCopy() 561 fCopy.Spec.Replicas += 10 562 fleetWatch.Modify(fCopy) 563 assert.Equal(t, expected, f()) 564 565 // test add/update of gameserver set 566 gsSet := fleet.GameServerSet() 567 gsSet.ObjectMeta.Name = "gs1" 568 gsSet.ObjectMeta.GenerateName = "" 569 gsSetWatch.Add(gsSet) 570 assert.Equal(t, expected, f()) 571 572 gsSet.Spec.Replicas += 10 573 gsSetWatch.Modify(gsSet) 574 assert.Equal(t, expected, f()) 575 } 576 577 func TestControllerUpdateFleetStatus(t *testing.T) { 578 t.Parallel() 579 580 t.Run("update, no errors", func(t *testing.T) { 581 fleet := defaultFixture() 582 c, m := newFakeController() 583 584 gsSet1 := fleet.GameServerSet() 585 gsSet1.ObjectMeta.Name = "gsSet1" 586 gsSet1.Status.Replicas = 3 587 gsSet1.Status.ReadyReplicas = 2 588 gsSet1.Status.ReservedReplicas = 4 589 gsSet1.Status.AllocatedReplicas = 1 590 591 gsSet2 := fleet.GameServerSet() 592 // nolint:goconst 593 gsSet2.ObjectMeta.Name = "gsSet2" 594 gsSet2.Status.Replicas = 5 595 gsSet2.Status.ReadyReplicas = 5 596 gsSet2.Status.ReservedReplicas = 3 597 gsSet2.Status.AllocatedReplicas = 2 598 599 m.AgonesClient.AddReactor("list", "gameserversets", 600 func(_ k8stesting.Action) (bool, runtime.Object, error) { 601 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil 602 }) 603 604 updated := false 605 m.AgonesClient.AddReactor("update", "fleets", 606 func(action k8stesting.Action) (bool, runtime.Object, error) { 607 updated = true 608 ua := action.(k8stesting.UpdateAction) 609 fleet := ua.GetObject().(*agonesv1.Fleet) 610 611 assert.Equal(t, gsSet1.Status.Replicas+gsSet2.Status.Replicas, fleet.Status.Replicas) 612 assert.Equal(t, gsSet1.Status.ReadyReplicas+gsSet2.Status.ReadyReplicas, fleet.Status.ReadyReplicas) 613 assert.Equal(t, gsSet1.Status.ReservedReplicas+gsSet2.Status.ReservedReplicas, fleet.Status.ReservedReplicas) 614 assert.Equal(t, gsSet1.Status.AllocatedReplicas+gsSet2.Status.AllocatedReplicas, fleet.Status.AllocatedReplicas) 615 return true, fleet, nil 616 }) 617 618 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 619 defer cancel() 620 621 err := c.updateFleetStatus(ctx, fleet) 622 assert.Nil(t, err) 623 assert.True(t, updated) 624 }) 625 626 t.Run("list gameservers returns an error", func(t *testing.T) { 627 fleet := defaultFixture() 628 c, _ := newFakeController() 629 c.gameServerSetLister = &fakeGSSListerWithErr{} 630 631 err := c.updateFleetStatus(context.Background(), fleet) 632 assert.EqualError(t, err, "error listing gameserversets for fleet fleet-1: random-err") 633 }) 634 635 t.Run("fleets getter returns an error", func(t *testing.T) { 636 fleet := defaultFixture() 637 c, _ := newFakeController() 638 639 c.fleetGetter = &fakeFleetsGetterWithErr{} 640 641 err := c.updateFleetStatus(context.Background(), fleet) 642 643 assert.EqualError(t, err, "err-from-fleet-getter") 644 }) 645 646 } 647 648 func TestControllerUpdateFleetPlayerStatus(t *testing.T) { 649 t.Parallel() 650 651 utilruntime.FeatureTestMutex.Lock() 652 defer utilruntime.FeatureTestMutex.Unlock() 653 654 require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeaturePlayerTracking)+"=true")) 655 656 fleet := defaultFixture() 657 c, m := newFakeController() 658 659 gsSet1 := fleet.GameServerSet() 660 gsSet1.ObjectMeta.Name = "gsSet1" 661 gsSet1.Status.Players = &agonesv1.AggregatedPlayerStatus{ 662 Count: 5, 663 Capacity: 10, 664 } 665 666 gsSet2 := fleet.GameServerSet() 667 gsSet2.ObjectMeta.Name = "gsSet2" 668 gsSet2.Status.Players = &agonesv1.AggregatedPlayerStatus{ 669 Count: 10, 670 Capacity: 20, 671 } 672 673 m.AgonesClient.AddReactor("list", "gameserversets", 674 func(_ k8stesting.Action) (bool, runtime.Object, error) { 675 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil 676 }) 677 678 updated := false 679 m.AgonesClient.AddReactor("update", "fleets", 680 func(action k8stesting.Action) (bool, runtime.Object, error) { 681 updated = true 682 ua := action.(k8stesting.UpdateAction) 683 fleet := ua.GetObject().(*agonesv1.Fleet) 684 685 assert.Equal(t, gsSet1.Status.Players.Count+gsSet2.Status.Players.Count, fleet.Status.Players.Count) 686 assert.Equal(t, gsSet1.Status.Players.Capacity+gsSet2.Status.Players.Capacity, fleet.Status.Players.Capacity) 687 688 return true, fleet, nil 689 }) 690 691 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 692 defer cancel() 693 694 err := c.updateFleetStatus(ctx, fleet) 695 assert.Nil(t, err) 696 assert.True(t, updated) 697 } 698 699 // nolint:dupl // Linter errors on lines are duplicate of TestControllerUpdateFleetListStatus 700 func TestControllerUpdateFleetCounterStatus(t *testing.T) { 701 t.Parallel() 702 703 utilruntime.FeatureTestMutex.Lock() 704 defer utilruntime.FeatureTestMutex.Unlock() 705 706 require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeatureCountsAndLists)+"=true")) 707 708 fleet := defaultFixture() 709 c, m := newFakeController() 710 711 gsSet1 := fleet.GameServerSet() 712 gsSet1.ObjectMeta.Name = "gsSet1" 713 gsSet1.Status.Counters = map[string]agonesv1.AggregatedCounterStatus{ 714 "fullCounter": { 715 AllocatedCount: 9223372036854775807, 716 AllocatedCapacity: 9223372036854775807, 717 Capacity: 9223372036854775807, 718 Count: 9223372036854775807, 719 }, 720 "anotherCounter": { 721 AllocatedCount: 11, 722 AllocatedCapacity: 20, 723 Capacity: 100, 724 Count: 42, 725 }, 726 } 727 728 gsSet2 := fleet.GameServerSet() 729 gsSet2.ObjectMeta.Name = "gsSet2" 730 gsSet2.Status.Counters = map[string]agonesv1.AggregatedCounterStatus{ 731 "fullCounter": { 732 AllocatedCount: 100, 733 AllocatedCapacity: 100, 734 Capacity: 100, 735 Count: 100, 736 }, 737 "anotherCounter": { 738 AllocatedCount: 0, 739 AllocatedCapacity: 0, 740 Capacity: 100, 741 Count: 0, 742 }, 743 "thirdCounter": { 744 AllocatedCount: 21, 745 AllocatedCapacity: 30, 746 Capacity: 400, 747 Count: 21, 748 }, 749 } 750 751 m.AgonesClient.AddReactor("list", "gameserversets", 752 func(_ k8stesting.Action) (bool, runtime.Object, error) { 753 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil 754 }) 755 756 updated := false 757 m.AgonesClient.AddReactor("update", "fleets", 758 func(action k8stesting.Action) (bool, runtime.Object, error) { 759 updated = true 760 ua := action.(k8stesting.UpdateAction) 761 fleet := ua.GetObject().(*agonesv1.Fleet) 762 763 assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].AllocatedCount) 764 assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].AllocatedCapacity) 765 assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].Capacity) 766 assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].Count) 767 768 assert.Equal(t, int64(11), fleet.Status.Counters["anotherCounter"].AllocatedCount) 769 assert.Equal(t, int64(20), fleet.Status.Counters["anotherCounter"].AllocatedCapacity) 770 assert.Equal(t, int64(200), fleet.Status.Counters["anotherCounter"].Capacity) 771 assert.Equal(t, int64(42), fleet.Status.Counters["anotherCounter"].Count) 772 773 assert.Equal(t, int64(21), fleet.Status.Counters["thirdCounter"].AllocatedCount) 774 assert.Equal(t, int64(30), fleet.Status.Counters["thirdCounter"].AllocatedCapacity) 775 assert.Equal(t, int64(400), fleet.Status.Counters["thirdCounter"].Capacity) 776 assert.Equal(t, int64(21), fleet.Status.Counters["thirdCounter"].Count) 777 778 return true, fleet, nil 779 }) 780 781 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 782 defer cancel() 783 784 err := c.updateFleetStatus(ctx, fleet) 785 assert.Nil(t, err) 786 assert.True(t, updated) 787 } 788 789 // nolint:dupl // Linter errors on lines are duplicate of TestControllerUpdateFleetCounterStatus 790 func TestControllerUpdateFleetListStatus(t *testing.T) { 791 t.Parallel() 792 793 utilruntime.FeatureTestMutex.Lock() 794 defer utilruntime.FeatureTestMutex.Unlock() 795 796 require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeatureCountsAndLists)+"=true")) 797 798 fleet := defaultFixture() 799 c, m := newFakeController() 800 801 gsSet1 := fleet.GameServerSet() 802 gsSet1.ObjectMeta.Name = "gsSet1" 803 gsSet1.Status.Lists = map[string]agonesv1.AggregatedListStatus{ 804 "fullList": { 805 AllocatedCount: 1000, 806 AllocatedCapacity: 1000, 807 Capacity: 1000, 808 Count: 1000, 809 }, 810 "anotherList": { 811 AllocatedCount: 11, 812 AllocatedCapacity: 100, 813 Capacity: 100, 814 Count: 11, 815 }, 816 "thirdList": { 817 AllocatedCount: 1, 818 AllocatedCapacity: 20, 819 Capacity: 30, 820 Count: 4, 821 }, 822 } 823 824 gsSet2 := fleet.GameServerSet() 825 gsSet2.ObjectMeta.Name = "gsSet2" 826 gsSet2.Status.Lists = map[string]agonesv1.AggregatedListStatus{ 827 "fullList": { 828 AllocatedCount: 200, 829 AllocatedCapacity: 200, 830 Capacity: 200, 831 Count: 200, 832 }, 833 "anotherList": { 834 AllocatedCount: 1, 835 AllocatedCapacity: 10, 836 Capacity: 100, 837 Count: 11, 838 }, 839 } 840 841 m.AgonesClient.AddReactor("list", "gameserversets", 842 func(_ k8stesting.Action) (bool, runtime.Object, error) { 843 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil 844 }) 845 846 updated := false 847 m.AgonesClient.AddReactor("update", "fleets", 848 func(action k8stesting.Action) (bool, runtime.Object, error) { 849 updated = true 850 ua := action.(k8stesting.UpdateAction) 851 fleet := ua.GetObject().(*agonesv1.Fleet) 852 853 assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].AllocatedCount) 854 assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].AllocatedCapacity) 855 assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].Capacity) 856 assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].Count) 857 858 assert.Equal(t, int64(12), fleet.Status.Lists["anotherList"].AllocatedCount) 859 assert.Equal(t, int64(110), fleet.Status.Lists["anotherList"].AllocatedCapacity) 860 assert.Equal(t, int64(200), fleet.Status.Lists["anotherList"].Capacity) 861 assert.Equal(t, int64(22), fleet.Status.Lists["anotherList"].Count) 862 863 assert.Equal(t, int64(1), fleet.Status.Lists["thirdList"].AllocatedCount) 864 assert.Equal(t, int64(20), fleet.Status.Lists["thirdList"].AllocatedCapacity) 865 assert.Equal(t, int64(30), fleet.Status.Lists["thirdList"].Capacity) 866 assert.Equal(t, int64(4), fleet.Status.Lists["thirdList"].Count) 867 868 return true, fleet, nil 869 }) 870 871 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 872 defer cancel() 873 874 err := c.updateFleetStatus(ctx, fleet) 875 assert.Nil(t, err) 876 assert.True(t, updated) 877 } 878 879 // Test that the aggregated Counters and Lists are removed from the Fleet status if the 880 // FeatureCountsAndLists flag is set to false. 881 func TestFleetDropCountsAndListsStatus(t *testing.T) { 882 t.Parallel() 883 884 utilruntime.FeatureTestMutex.Lock() 885 defer utilruntime.FeatureTestMutex.Unlock() 886 887 f := defaultFixture() 888 defaultFleetName := "default/fleet-1" 889 c, m := newFakeController() 890 891 gss := f.GameServerSet() 892 gss.ObjectMeta.Name = "gsSet1" 893 gss.ObjectMeta.UID = "4321" 894 gss.Spec.Replicas = f.Spec.Replicas 895 gss.Status.Counters = map[string]agonesv1.AggregatedCounterStatus{ 896 "aCounter": { 897 AllocatedCount: 1, 898 AllocatedCapacity: 10, 899 Capacity: 1000, 900 Count: 100, 901 }, 902 } 903 gss.Status.Lists = map[string]agonesv1.AggregatedListStatus{ 904 "aList": { 905 AllocatedCount: 10, 906 AllocatedCapacity: 100, 907 Capacity: 10000, 908 Count: 1000, 909 }, 910 } 911 912 flag := "" 913 updated := false 914 915 m.AgonesClient.AddReactor("list", "gameserversets", 916 func(_ k8stesting.Action) (bool, runtime.Object, error) { 917 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gss}}, nil 918 }) 919 920 m.AgonesClient.AddReactor("list", "fleets", 921 func(_ k8stesting.Action) (bool, runtime.Object, error) { 922 return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil 923 }) 924 925 m.AgonesClient.AddReactor("update", "fleets", 926 func(action k8stesting.Action) (bool, runtime.Object, error) { 927 ua := action.(k8stesting.UpdateAction) 928 fleet := ua.GetObject().(*agonesv1.Fleet) 929 updated = true 930 931 switch flag { 932 case string(utilruntime.FeatureCountsAndLists) + "=true": 933 assert.Equal(t, gss.Status.Counters, fleet.Status.Counters) 934 assert.Equal(t, gss.Status.Lists, fleet.Status.Lists) 935 case string(utilruntime.FeatureCountsAndLists) + "=false": 936 assert.Nil(t, fleet.Status.Counters) 937 assert.Nil(t, fleet.Status.Lists) 938 default: 939 return false, fleet, errors.Errorf("Flag string(utilruntime.FeatureCountsAndLists) should be set") 940 } 941 return true, fleet, nil 942 }) 943 944 ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced) 945 defer cancel() 946 947 // Expect starting fleet to have Aggregated Counter and List Statuses 948 flag = string(utilruntime.FeatureCountsAndLists) + "=true" 949 require.NoError(t, utilruntime.ParseFeatures(flag)) 950 err := c.syncFleet(ctx, defaultFleetName) 951 assert.NoError(t, err) 952 assert.True(t, updated) 953 954 updated = false 955 flag = string(utilruntime.FeatureCountsAndLists) + "=false" 956 require.NoError(t, utilruntime.ParseFeatures(flag)) 957 err = c.syncFleet(ctx, defaultFleetName) 958 assert.NoError(t, err) 959 assert.True(t, updated) 960 961 updated = false 962 flag = string(utilruntime.FeatureCountsAndLists) + "=true" 963 require.NoError(t, utilruntime.ParseFeatures(flag)) 964 err = c.syncFleet(ctx, defaultFleetName) 965 assert.NoError(t, err) 966 assert.True(t, updated) 967 } 968 969 func TestControllerFilterGameServerSetByActive(t *testing.T) { 970 t.Parallel() 971 972 f := defaultFixture() 973 c, _ := newFakeController() 974 // the same GameServer Template 975 gsSet1 := f.GameServerSet() 976 gsSet1.ObjectMeta.Name = "gsSet1" 977 978 // different GameServer Template 979 gsSet2 := f.GameServerSet() 980 gsSet2.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 9999}} 981 982 // one active 983 active, rest := c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet1, gsSet2}) 984 assert.Equal(t, gsSet1, active) 985 assert.Equal(t, []*agonesv1.GameServerSet{gsSet2}, rest) 986 987 // none active 988 gsSet1.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 9999}} 989 active, rest = c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet1, gsSet2}) 990 assert.Nil(t, active) 991 assert.Equal(t, []*agonesv1.GameServerSet{gsSet1, gsSet2}, rest) 992 } 993 994 func TestControllerRecreateDeployment(t *testing.T) { 995 t.Parallel() 996 997 f := defaultFixture() 998 f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType 999 f.Spec.Replicas = 10 1000 gsSet1 := f.GameServerSet() 1001 gsSet1.ObjectMeta.Name = "gsSet1" 1002 gsSet1.Spec.Replicas = 10 1003 gsSet2 := f.GameServerSet() 1004 gsSet2.ObjectMeta.Name = "gsSet2" 1005 gsSet2.Spec.Replicas = 0 1006 gsSet2.Status.AllocatedReplicas = 1 1007 1008 t.Run("ok scenario", func(t *testing.T) { 1009 c, m := newFakeController() 1010 updated := false 1011 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1012 updated = true 1013 ua := action.(k8stesting.UpdateAction) 1014 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 1015 assert.Equal(t, gsSet1.ObjectMeta.Name, gsSet.ObjectMeta.Name) 1016 assert.Equal(t, int32(0), gsSet.Spec.Replicas) 1017 1018 return true, gsSet, nil 1019 }) 1020 1021 replicas, err := c.recreateDeployment(context.Background(), f, []*agonesv1.GameServerSet{gsSet1, gsSet2}) 1022 1023 require.NoError(t, err) 1024 assert.True(t, updated) 1025 assert.Equal(t, f.Spec.Replicas-1, replicas) 1026 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 1027 }) 1028 1029 t.Run("error on update", func(t *testing.T) { 1030 c, m := newFakeController() 1031 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1032 return true, nil, errors.New("random-err") 1033 }) 1034 1035 _, err := c.recreateDeployment(context.Background(), f, []*agonesv1.GameServerSet{gsSet1, gsSet2}) 1036 1037 assert.EqualError(t, err, "error updating gameserverset gsSet1: random-err") 1038 }) 1039 } 1040 1041 func TestControllerApplyDeploymentStrategy(t *testing.T) { 1042 t.Parallel() 1043 1044 type expected struct { 1045 inactiveReplicas int32 1046 replicas int32 1047 } 1048 1049 fixtures := map[string]struct { 1050 strategyType appsv1.DeploymentStrategyType 1051 gsSet1StatusReplicas int32 1052 gsSet2StatusReplicas int32 1053 expected expected 1054 }{ 1055 string(appsv1.RecreateDeploymentStrategyType): { 1056 strategyType: appsv1.RecreateDeploymentStrategyType, 1057 gsSet1StatusReplicas: 0, 1058 gsSet2StatusReplicas: 0, 1059 expected: expected{ 1060 inactiveReplicas: 0, 1061 replicas: 10, 1062 }, 1063 }, 1064 string(appsv1.RollingUpdateDeploymentStrategyType): { 1065 strategyType: appsv1.RollingUpdateDeploymentStrategyType, 1066 gsSet1StatusReplicas: 10, 1067 gsSet2StatusReplicas: 1, 1068 expected: expected{ 1069 inactiveReplicas: 8, 1070 replicas: 2, 1071 }, 1072 }, 1073 } 1074 1075 for k, v := range fixtures { 1076 t.Run(k, func(t *testing.T) { 1077 if utilruntime.FeatureEnabled(utilruntime.FeatureRollingUpdateFix) { 1078 t.SkipNow() 1079 } 1080 1081 f := defaultFixture() 1082 f.Spec.Strategy.Type = v.strategyType 1083 f.Spec.Replicas = 10 1084 1085 gsSet1 := f.GameServerSet() 1086 gsSet1.ObjectMeta.Name = "gsSet1" 1087 gsSet1.Spec.Replicas = 10 1088 gsSet1.Status.Replicas = v.gsSet1StatusReplicas 1089 1090 gsSet2 := f.GameServerSet() 1091 gsSet2.ObjectMeta.Name = "gsSet2" 1092 gsSet2.Spec.Replicas = 0 1093 gsSet2.Status.Replicas = v.gsSet2StatusReplicas 1094 1095 c, m := newFakeController() 1096 1097 updated := false 1098 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1099 updated = true 1100 ua := action.(k8stesting.UpdateAction) 1101 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 1102 assert.Equal(t, gsSet1.ObjectMeta.Name, gsSet.ObjectMeta.Name) 1103 assert.Equal(t, v.expected.inactiveReplicas, gsSet.Spec.Replicas) 1104 1105 return true, gsSet, nil 1106 }) 1107 1108 replicas, err := c.applyDeploymentStrategy(context.Background(), f, f.GameServerSet(), []*agonesv1.GameServerSet{gsSet1, gsSet2}) 1109 require.NoError(t, err) 1110 assert.True(t, updated, "update should happen") 1111 assert.Equal(t, v.expected.replicas, replicas) 1112 }) 1113 } 1114 1115 t.Run("a single gameserverset", func(t *testing.T) { 1116 f := defaultFixture() 1117 f.Spec.Replicas = 10 1118 1119 gsSet1 := f.GameServerSet() 1120 gsSet1.ObjectMeta.Name = "gsSet1" 1121 1122 c, _ := newFakeController() 1123 1124 replicas, err := c.applyDeploymentStrategy(context.Background(), f, f.GameServerSet(), []*agonesv1.GameServerSet{}) 1125 require.NoError(t, err) 1126 assert.Equal(t, f.Spec.Replicas, replicas) 1127 }) 1128 1129 t.Run("rest gameservers that are already scaled down", func(t *testing.T) { 1130 f := defaultFixture() 1131 f.Spec.Replicas = 10 1132 1133 gsSet1 := f.GameServerSet() 1134 gsSet1.ObjectMeta.Name = "gsSet1" 1135 gsSet1.Spec.Replicas = 0 1136 gsSet1.Status.AllocatedReplicas = 1 1137 1138 c, _ := newFakeController() 1139 1140 replicas, err := c.applyDeploymentStrategy(context.Background(), f, f.GameServerSet(), []*agonesv1.GameServerSet{gsSet1}) 1141 require.NoError(t, err) 1142 assert.Equal(t, int32(9), replicas) 1143 }) 1144 } 1145 1146 func TestControllerUpsertGameServerSet(t *testing.T) { 1147 t.Parallel() 1148 f := defaultFixture() 1149 replicas := int32(10) 1150 1151 t.Run("insert", func(t *testing.T) { 1152 c, m := newFakeController() 1153 gsSet := f.GameServerSet() 1154 created := false 1155 m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1156 created = true 1157 ca := action.(k8stesting.CreateAction) 1158 gsSet := ca.GetObject().(*agonesv1.GameServerSet) 1159 assert.Equal(t, replicas, gsSet.Spec.Replicas) 1160 1161 return true, gsSet, nil 1162 }) 1163 1164 err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas) 1165 assert.Nil(t, err) 1166 1167 assert.True(t, created, "Should be created") 1168 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "CreatingGameServerSet") 1169 }) 1170 1171 t.Run("update", func(t *testing.T) { 1172 c, m := newFakeController() 1173 gsSet := f.GameServerSet() 1174 gsSet.ObjectMeta.UID = "1234" 1175 gsSet.Spec.Replicas = replicas + 10 1176 update := false 1177 1178 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1179 update = true 1180 ca := action.(k8stesting.UpdateAction) 1181 gsSet := ca.GetObject().(*agonesv1.GameServerSet) 1182 assert.Equal(t, replicas, gsSet.Spec.Replicas) 1183 1184 return true, gsSet, nil 1185 }) 1186 1187 err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas) 1188 assert.Nil(t, err) 1189 1190 assert.True(t, update, "Should be updated") 1191 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 1192 }) 1193 1194 t.Run("error updating gss replicas", func(t *testing.T) { 1195 c, m := newFakeController() 1196 gsSet := f.GameServerSet() 1197 gsSet.ObjectMeta.UID = "1234" 1198 gsSet.Spec.Replicas = replicas + 10 1199 1200 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1201 return true, nil, errors.New("random-err") 1202 }) 1203 1204 err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas) 1205 1206 assert.EqualError(t, err, "error updating replicas for gameserverset for fleet fleet-1: random-err") 1207 }) 1208 1209 t.Run("error on gs status update", func(t *testing.T) { 1210 c, m := newFakeController() 1211 gsSet := f.GameServerSet() 1212 1213 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1214 return true, nil, errors.New("random-err") 1215 }) 1216 1217 err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas) 1218 1219 assert.EqualError(t, err, "error updating status of gameserverset for fleet fleet-1: random-err") 1220 }) 1221 1222 t.Run("nothing happens, nil is returned", func(t *testing.T) { 1223 t.Parallel() 1224 1225 c, m := newFakeController() 1226 gsSet := f.GameServerSet() 1227 gsSet.ObjectMeta.UID = "1234" 1228 gsSet.Spec.Replicas = replicas 1229 1230 m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1231 assert.FailNow(t, "should not create") 1232 return false, nil, nil 1233 }) 1234 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1235 assert.FailNow(t, "should not update") 1236 return false, nil, nil 1237 }) 1238 1239 err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas) 1240 assert.Nil(t, err) 1241 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 1242 }) 1243 1244 t.Run("update Priorities", func(t *testing.T) { 1245 utilruntime.FeatureTestMutex.Lock() 1246 defer utilruntime.FeatureTestMutex.Unlock() 1247 require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeatureCountsAndLists)+"=true")) 1248 1249 c, m := newFakeController() 1250 // Default GameServerSet has no Priorities 1251 gsSet := f.GameServerSet() 1252 gsSet.ObjectMeta.UID = "1234" 1253 // Add Priorities to the Fleet 1254 f.Spec.Priorities = []agonesv1.Priority{ 1255 { 1256 Type: "List", 1257 Key: "Baz", 1258 Order: "Ascending", 1259 }} 1260 update := false 1261 1262 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1263 update = true 1264 ca := action.(k8stesting.UpdateAction) 1265 gsSet := ca.GetObject().(*agonesv1.GameServerSet) 1266 assert.Equal(t, agonesv1.Priority{Type: "List", Key: "Baz", Order: "Ascending"}, gsSet.Spec.Priorities[0]) 1267 return true, gsSet, nil 1268 }) 1269 1270 // Update Priorities on the GameServerSet to match the Fleet 1271 err := c.upsertGameServerSet(context.Background(), f, gsSet, gsSet.Spec.Replicas) 1272 assert.Nil(t, err) 1273 1274 assert.True(t, update, "Should be updated") 1275 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "UpdatingGameServerSet") 1276 }) 1277 } 1278 1279 func TestResourcesRequestsAndLimits(t *testing.T) { 1280 t.Parallel() 1281 1282 gsSpec := *defaultGSSpec() 1283 c, _ := newFakeController() 1284 f := defaultFixture() 1285 f.Spec.Template = gsSpec 1286 f.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1000m") 1287 1288 // Semantically equal definition, 1 == 1000m CPU 1289 gsSet1 := f.GameServerSet() 1290 gsSet1.Spec.Template = gsSpec 1291 gsSet1.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1") 1292 1293 // Absolutely different GameServer Spec, 1.1 CPU 1294 gsSet3 := f.GameServerSet() 1295 gsSet3.Spec.Template = *gsSpec.DeepCopy() 1296 gsSet3.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1.1") 1297 active, rest := c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet1, gsSet3}) 1298 assert.Equal(t, gsSet1, active) 1299 assert.Equal(t, []*agonesv1.GameServerSet{gsSet3}, rest) 1300 1301 gsSet2 := f.GameServerSet() 1302 gsSet2.Spec.Template = *gsSpec.DeepCopy() 1303 gsSet2.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1000m") 1304 active, rest = c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet2, gsSet3}) 1305 assert.Equal(t, gsSet2, active) 1306 assert.Equal(t, []*agonesv1.GameServerSet{gsSet3}, rest) 1307 } 1308 1309 func TestControllerDeleteEmptyGameServerSets(t *testing.T) { 1310 t.Parallel() 1311 1312 f := defaultFixture() 1313 gsSet1 := f.GameServerSet() 1314 gsSet1.ObjectMeta.Name = "gsSet1" 1315 gsSet1.Spec.Replicas = 10 1316 gsSet1.Status.Replicas = 10 1317 gsSet2 := f.GameServerSet() 1318 gsSet2.ObjectMeta.Name = "gsSet2" 1319 gsSet2.Spec.Replicas = 0 1320 gsSet2.Status.Replicas = 0 1321 1322 c, m := newFakeController() 1323 deleted := false 1324 1325 m.AgonesClient.AddReactor("delete", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1326 deleted = true 1327 da := action.(k8stesting.DeleteAction) 1328 assert.Equal(t, gsSet2.ObjectMeta.Name, da.GetName()) 1329 return true, nil, nil 1330 }) 1331 1332 err := c.deleteEmptyGameServerSets(context.Background(), f, []*agonesv1.GameServerSet{gsSet1, gsSet2}) 1333 assert.Nil(t, err) 1334 assert.True(t, deleted, "delete should happen") 1335 } 1336 1337 func TestControllerRollingUpdateDeploymentNoInactiveGSSNoErrors(t *testing.T) { 1338 t.Parallel() 1339 1340 f := defaultFixture() 1341 1342 f.Spec.Replicas = 100 1343 1344 active := f.GameServerSet() 1345 active.ObjectMeta.Name = "active" 1346 1347 c, _ := newFakeController() 1348 1349 replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{}) 1350 assert.Nil(t, err) 1351 assert.Equal(t, int32(25), replicas) 1352 } 1353 1354 // Test when replicas is negative value(0 replicas - 1 allocated = -1) 1355 func TestControllerRollingUpdateDeploymentNegativeReplica(t *testing.T) { 1356 t.Parallel() 1357 1358 // Create Fleet with replicas: 5 1359 f := defaultFixture() 1360 f.Status.Replicas = 5 1361 // Allocate 1 gameserver 1362 f.Status.AllocatedReplicas = 1 1363 f.Status.ReadyReplicas = 4 1364 1365 // Edit fleet spec.template.spec and create new gameserverset 1366 f.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{ 1367 ContainerPort: 6000, 1368 Name: "gameport", 1369 PortPolicy: agonesv1.Dynamic, 1370 Protocol: corev1.ProtocolUDP, 1371 }} 1372 1373 // old gameserverset has only allocated gameserver 1374 inactive := f.GameServerSet() 1375 inactive.ObjectMeta.Name = "inactive" 1376 inactive.Spec.Replicas = 0 1377 inactive.Status.ReadyReplicas = 0 1378 inactive.Status.Replicas = 1 1379 inactive.Status.AllocatedReplicas = 1 1380 1381 // new gameserverset has 4 gameserver(replicas:5 - sumAllocated:1) 1382 active := f.GameServerSet() 1383 active.ObjectMeta.Name = "active" 1384 active.Spec.Replicas = 4 1385 active.Status.ReadyReplicas = 4 1386 active.Status.Replicas = 4 1387 active.Status.AllocatedReplicas = 0 1388 1389 c, m := newFakeController() 1390 1391 // triggered inside rollingUpdateRest 1392 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1393 ca := action.(k8stesting.UpdateAction) 1394 gsSet := ca.GetObject().(*agonesv1.GameServerSet) 1395 assert.Equal(t, int32(4), gsSet.Spec.Replicas) 1396 assert.Equal(t, int32(5), f.Spec.Replicas) 1397 1398 return true, nil, errors.Errorf("error updating replicas for gameserverset for fleet %s", f.Name) 1399 }) 1400 1401 // assert the active gameserverset's replicas when active and inactive gameserversets exist 1402 expected := f.Spec.Replicas - f.Status.AllocatedReplicas 1403 replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive}) 1404 f.Status.ReadyReplicas = replicas 1405 assert.NoError(t, err) 1406 assert.Equal(t, expected, replicas) 1407 1408 // happened scale down to 0 by manual operation 1409 f.Spec.Replicas = 0 1410 // rolling update to scale 0 1411 replicas, err = c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive}) 1412 f.Status.ReadyReplicas = replicas 1413 // assert no error, when fleet replicas is negative value(0 replicas - 1 allocated = -1) 1414 assert.NoError(t, err) 1415 // assert replicas 0, after user scales replicas to 0 1416 assert.Equal(t, int32(0), replicas) 1417 } 1418 1419 func TestControllerRollingUpdateDeploymentGSSUpdateFailedErrExpected(t *testing.T) { 1420 t.Parallel() 1421 1422 f := defaultFixture() 1423 f.Spec.Replicas = 75 1424 f.Status.ReadyReplicas = 75 1425 1426 active := f.GameServerSet() 1427 active.ObjectMeta.Name = "active" 1428 active.Spec.Replicas = 75 1429 active.Status.ReadyReplicas = 75 1430 active.Status.Replicas = 75 1431 1432 inactive := f.GameServerSet() 1433 inactive.ObjectMeta.Name = "inactive" 1434 inactive.Spec.Replicas = 10 1435 inactive.Status.ReadyReplicas = 10 1436 inactive.Status.Replicas = 10 1437 inactive.Status.AllocatedReplicas = 5 1438 1439 c, m := newFakeController() 1440 1441 // triggered inside rollingUpdateRest 1442 m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1443 return true, nil, errors.New("random-err") 1444 }) 1445 1446 _, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive}) 1447 assert.EqualError(t, err, "error updating gameserverset inactive: random-err") 1448 } 1449 1450 func TestRollingUpdateOnReady(t *testing.T) { 1451 type expected struct { 1452 inactiveSpecReplicas int32 1453 replicas int32 1454 updated bool 1455 } 1456 1457 fixtures := map[string]struct { 1458 activeStatusReadyReplicas int32 1459 inactiveStatusReadyReplicas int32 1460 allocatedReplicas int32 1461 expected expected 1462 }{ 1463 "not enough Ready GameServers - do not scale down rest GameServerSet": { 1464 activeStatusReadyReplicas: 10, 1465 inactiveStatusReadyReplicas: 10, 1466 expected: expected{ 1467 updated: false, 1468 inactiveSpecReplicas: 0, 1469 replicas: 75, 1470 }, 1471 }, 1472 "enough Ready GameServers - scale down rest GameServerSet to Allocated": { 1473 activeStatusReadyReplicas: 70, 1474 inactiveStatusReadyReplicas: 5, 1475 allocatedReplicas: 5, 1476 expected: expected{ 1477 updated: true, 1478 inactiveSpecReplicas: 5, 1479 replicas: 70, 1480 }, 1481 }, 1482 "enough Ready GameServers - scale down rest GameServerSet to 0": { 1483 activeStatusReadyReplicas: 70, 1484 inactiveStatusReadyReplicas: 10, 1485 allocatedReplicas: 0, 1486 expected: expected{ 1487 updated: true, 1488 inactiveSpecReplicas: 0, 1489 replicas: 75, 1490 }, 1491 }, 1492 "scale down rest GameServerSet to > 0": { 1493 // 75 - 19 = 56 is minimum number of gameservers 1494 // scaling 58 - 56 = -2 gameservers 1495 // initial 10 - 2 = 8 1496 activeStatusReadyReplicas: 50, 1497 inactiveStatusReadyReplicas: 8, 1498 allocatedReplicas: 0, 1499 expected: expected{ 1500 updated: true, 1501 inactiveSpecReplicas: 8, 1502 replicas: 75, 1503 }, 1504 }, 1505 } 1506 1507 for k, v := range fixtures { 1508 t.Run(k, func(t *testing.T) { 1509 c, m := newFakeController() 1510 1511 f := defaultFixture() 1512 f.Spec.Replicas = 75 1513 f.Status.ReadyReplicas = v.activeStatusReadyReplicas + v.inactiveStatusReadyReplicas 1514 1515 active := f.GameServerSet() 1516 active.ObjectMeta.Name = "active" 1517 active.Spec.Replicas = 75 1518 active.Status.Replicas = 75 1519 active.Status.ReadyReplicas = v.activeStatusReadyReplicas 1520 1521 inactive := f.GameServerSet() 1522 inactive.ObjectMeta.Name = "inactive" 1523 inactive.Spec.Replicas = 10 1524 inactive.Status.Replicas = 10 1525 inactive.Status.ReadyReplicas = v.inactiveStatusReadyReplicas 1526 inactive.Status.AllocatedReplicas = v.allocatedReplicas 1527 updated := false 1528 // triggered inside rollingUpdateRest 1529 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1530 updated = true 1531 ua := action.(k8stesting.UpdateAction) 1532 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 1533 assert.Equal(t, inactive.ObjectMeta.Name, gsSet.ObjectMeta.Name) 1534 assert.Equal(t, v.expected.inactiveSpecReplicas, gsSet.Spec.Replicas) 1535 1536 return true, gsSet, nil 1537 }) 1538 1539 replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive}) 1540 require.NoError(t, err, "no error") 1541 1542 assert.Equal(t, v.expected.replicas, replicas) 1543 assert.Equal(t, v.expected.updated, updated) 1544 if updated { 1545 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 1546 } else { 1547 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 1548 } 1549 }) 1550 } 1551 1552 } 1553 1554 func TestControllerRollingUpdateDeployment(t *testing.T) { 1555 t.Cleanup(func() { 1556 utilruntime.FeatureTestMutex.Lock() 1557 defer utilruntime.FeatureTestMutex.Unlock() 1558 require.NoError(t, utilruntime.ParseFeatures("")) 1559 }) 1560 1561 type expected struct { 1562 inactiveSpecReplicas int32 1563 replicas int32 1564 updated bool 1565 err string 1566 } 1567 1568 fixtures := map[string]struct { 1569 features string 1570 fleetSpecReplicas int32 1571 activeSpecReplicas int32 1572 activeStatusReplicas int32 1573 readyReplicas int32 1574 inactiveSpecReplicas int32 1575 inactiveStatusReplicas int32 1576 inactiveStatusReadyReplicas int32 1577 inactiveStatusAllocationReplicas int32 1578 nilMaxSurge bool 1579 nilMaxUnavailable bool 1580 expected expected 1581 }{ 1582 "nil MaxUnavailable, err expected": { 1583 fleetSpecReplicas: 100, 1584 activeSpecReplicas: 0, 1585 activeStatusReplicas: 0, 1586 inactiveSpecReplicas: 100, 1587 inactiveStatusReplicas: 100, 1588 inactiveStatusReadyReplicas: 100, 1589 nilMaxUnavailable: true, 1590 expected: expected{ 1591 err: "error parsing MaxUnavailable value: fleet-1: nil value for IntOrString", 1592 }, 1593 }, 1594 "nil MaxSurge, err expected": { 1595 fleetSpecReplicas: 100, 1596 activeSpecReplicas: 0, 1597 activeStatusReplicas: 0, 1598 inactiveSpecReplicas: 100, 1599 inactiveStatusReplicas: 100, 1600 inactiveStatusReadyReplicas: 100, 1601 nilMaxSurge: true, 1602 expected: expected{ 1603 err: "error parsing MaxSurge value: fleet-1: nil value for IntOrString", 1604 }, 1605 }, 1606 "full inactive, empty inactive": { 1607 fleetSpecReplicas: 100, 1608 activeSpecReplicas: 0, 1609 activeStatusReplicas: 0, 1610 inactiveSpecReplicas: 100, 1611 inactiveStatusReplicas: 100, 1612 inactiveStatusReadyReplicas: 100, 1613 expected: expected{ 1614 inactiveSpecReplicas: 70, 1615 replicas: 25, 1616 updated: true, 1617 }, 1618 }, 1619 "almost empty inactive with allocated, almost full active": { 1620 fleetSpecReplicas: 100, 1621 activeSpecReplicas: 75, 1622 activeStatusReplicas: 75, 1623 inactiveSpecReplicas: 10, 1624 inactiveStatusReplicas: 10, 1625 inactiveStatusReadyReplicas: 10, 1626 inactiveStatusAllocationReplicas: 5, 1627 1628 expected: expected{ 1629 inactiveSpecReplicas: 0, 1630 replicas: 95, 1631 updated: true, 1632 }, 1633 }, 1634 "attempt to drive replicas over the max surge": { 1635 features: "RollingUpdateFix=false", 1636 fleetSpecReplicas: 100, 1637 activeSpecReplicas: 25, 1638 activeStatusReplicas: 25, 1639 inactiveSpecReplicas: 95, 1640 inactiveStatusReplicas: 95, 1641 inactiveStatusReadyReplicas: 95, 1642 expected: expected{ 1643 inactiveSpecReplicas: 45, 1644 replicas: 30, 1645 updated: true, 1646 }, 1647 }, 1648 "test smalled numbers of active and allocated": { 1649 fleetSpecReplicas: 5, 1650 activeSpecReplicas: 0, 1651 activeStatusReplicas: 0, 1652 inactiveSpecReplicas: 5, 1653 inactiveStatusReplicas: 5, 1654 inactiveStatusReadyReplicas: 5, 1655 inactiveStatusAllocationReplicas: 2, 1656 expected: expected{ 1657 inactiveSpecReplicas: 4, 1658 replicas: 2, 1659 updated: true, 1660 }, 1661 }, 1662 "activeSpecReplicas >= (fleetSpecReplicas - inactiveStatusAllocationReplicas)": { 1663 fleetSpecReplicas: 75, 1664 activeSpecReplicas: 75, 1665 activeStatusReplicas: 75, 1666 inactiveSpecReplicas: 10, 1667 inactiveStatusReplicas: 10, 1668 inactiveStatusReadyReplicas: 10, 1669 inactiveStatusAllocationReplicas: 5, 1670 expected: expected{ 1671 inactiveSpecReplicas: 0, 1672 replicas: 70, 1673 updated: true, 1674 }, 1675 }, 1676 "rolling update does not remove all ready replicas": { 1677 features: "RollingUpdateFix=true", 1678 fleetSpecReplicas: 100, 1679 activeSpecReplicas: 0, 1680 activeStatusReplicas: 0, 1681 inactiveSpecReplicas: 100, 1682 inactiveStatusReplicas: 100, 1683 inactiveStatusReadyReplicas: 10, 1684 inactiveStatusAllocationReplicas: 90, 1685 expected: expected{ 1686 inactiveSpecReplicas: 97, 1687 replicas: 10, 1688 updated: true, 1689 }, 1690 }, 1691 "rolling update stops scaling fully allocated inactive": { 1692 features: "RollingUpdateFix=true", 1693 fleetSpecReplicas: 100, 1694 activeSpecReplicas: 50, 1695 activeStatusReplicas: 50, 1696 inactiveSpecReplicas: 50, 1697 inactiveStatusReplicas: 50, 1698 inactiveStatusReadyReplicas: 0, 1699 inactiveStatusAllocationReplicas: 50, 1700 expected: expected{ 1701 inactiveSpecReplicas: 0, 1702 replicas: 50, 1703 updated: true, 1704 }, 1705 }, 1706 "rolling update scales down with fleet spec replicas = 0": { 1707 features: "RollingUpdateFix=true", 1708 fleetSpecReplicas: 0, 1709 activeSpecReplicas: 0, 1710 activeStatusReplicas: 0, 1711 inactiveSpecReplicas: 3, 1712 inactiveStatusReplicas: 3, 1713 inactiveStatusReadyReplicas: 3, 1714 inactiveStatusAllocationReplicas: 0, 1715 expected: expected{ 1716 inactiveSpecReplicas: 0, 1717 replicas: 0, 1718 updated: true, 1719 }, 1720 }, 1721 } 1722 1723 for k, v := range fixtures { 1724 t.Run(k, func(t *testing.T) { 1725 utilruntime.FeatureTestMutex.Lock() 1726 defer utilruntime.FeatureTestMutex.Unlock() 1727 require.NoError(t, utilruntime.ParseFeatures(v.features)) 1728 1729 f := defaultFixture() 1730 1731 mu := intstr.FromString("30%") 1732 f.Spec.Strategy.RollingUpdate.MaxUnavailable = &mu 1733 f.Spec.Replicas = v.fleetSpecReplicas 1734 1735 // Inactive GameServerSet is downscaled second time only after 1736 // ReadyReplicas has raised. 1737 f.Status.ReadyReplicas = v.activeStatusReplicas + v.inactiveStatusReadyReplicas 1738 1739 if v.nilMaxSurge { 1740 f.Spec.Strategy.RollingUpdate.MaxSurge = nil 1741 } else { 1742 assert.Equal(t, "25%", f.Spec.Strategy.RollingUpdate.MaxSurge.String()) 1743 } 1744 1745 if v.nilMaxUnavailable { 1746 f.Spec.Strategy.RollingUpdate.MaxUnavailable = nil 1747 } else { 1748 assert.Equal(t, "30%", f.Spec.Strategy.RollingUpdate.MaxUnavailable.String()) 1749 } 1750 1751 active := f.GameServerSet() 1752 active.ObjectMeta.Name = "active" 1753 active.Spec.Replicas = v.activeSpecReplicas 1754 active.Status.Replicas = v.activeStatusReplicas 1755 active.Status.ReadyReplicas = v.activeStatusReplicas 1756 1757 inactive := f.GameServerSet() 1758 inactive.ObjectMeta.Name = "inactive" 1759 inactive.Spec.Replicas = v.inactiveSpecReplicas 1760 inactive.Status.Replicas = v.inactiveStatusReplicas 1761 inactive.Status.ReadyReplicas = v.inactiveStatusReadyReplicas 1762 inactive.Status.AllocatedReplicas = v.inactiveStatusAllocationReplicas 1763 1764 logrus.WithField("inactive", inactive).Info("Setting up the initial inactive") 1765 1766 updated := false 1767 c, m := newFakeController() 1768 1769 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1770 updated = true 1771 ua := action.(k8stesting.UpdateAction) 1772 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 1773 assert.Equal(t, inactive.ObjectMeta.Name, gsSet.ObjectMeta.Name) 1774 assert.Equal(t, v.expected.inactiveSpecReplicas, gsSet.Spec.Replicas) 1775 1776 return true, gsSet, nil 1777 }) 1778 1779 replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive}) 1780 1781 if v.expected.err != "" { 1782 assert.EqualError(t, err, v.expected.err) 1783 } else { 1784 require.NoError(t, err) 1785 assert.Equal(t, v.expected.replicas, replicas) 1786 assert.Equal(t, v.expected.updated, updated) 1787 if updated { 1788 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet") 1789 } else { 1790 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 1791 } 1792 } 1793 }) 1794 } 1795 } 1796 1797 // newFakeController returns a controller, backed by the fake Clientset 1798 func newFakeController() (*Controller, agtesting.Mocks) { 1799 m := agtesting.NewMocks() 1800 c := NewController(healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory) 1801 c.recorder = m.FakeRecorder 1802 return c, m 1803 } 1804 1805 // newFakeExtensions returns a fake extensions struct 1806 func newFakeExtensions() *Extensions { 1807 return NewExtensions(generic.New(), webhooks.NewWebHook(http.NewServeMux())) 1808 } 1809 1810 func defaultFixture() *agonesv1.Fleet { 1811 f := &agonesv1.Fleet{ 1812 ObjectMeta: metav1.ObjectMeta{ 1813 Name: "fleet-1", 1814 Namespace: "default", 1815 UID: "1234", 1816 }, 1817 Spec: agonesv1.FleetSpec{ 1818 Replicas: 5, 1819 Scheduling: apis.Packed, 1820 Template: agonesv1.GameServerTemplateSpec{}, 1821 }, 1822 } 1823 f.ApplyDefaults() 1824 return f 1825 } 1826 1827 func defaultGSSpec() *agonesv1.GameServerTemplateSpec { 1828 return &agonesv1.GameServerTemplateSpec{ 1829 Spec: agonesv1.GameServerSpec{ 1830 Container: "udp-server", 1831 Ports: []agonesv1.GameServerPort{{ 1832 ContainerPort: 7654, 1833 Name: "gameport", 1834 PortPolicy: agonesv1.Dynamic, 1835 Protocol: corev1.ProtocolUDP, 1836 }}, 1837 Template: corev1.PodTemplateSpec{ 1838 Spec: corev1.PodSpec{ 1839 Containers: []corev1.Container{{ 1840 Name: "udp-server", 1841 Image: "gcr.io/images/new:0.2", 1842 ImagePullPolicy: corev1.PullIfNotPresent, 1843 Resources: corev1.ResourceRequirements{ 1844 Requests: corev1.ResourceList{ 1845 corev1.ResourceCPU: resource.MustParse("30m"), 1846 corev1.ResourceMemory: resource.MustParse("32Mi"), 1847 }, 1848 Limits: corev1.ResourceList{ 1849 corev1.ResourceCPU: resource.MustParse("30m"), 1850 corev1.ResourceMemory: resource.MustParse("32Mi"), 1851 }, 1852 }, 1853 }}, 1854 }, 1855 }, 1856 }, 1857 } 1858 } 1859 1860 func getAdmissionReview(raw []byte) admissionv1.AdmissionReview { 1861 gvk := metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("Fleet")) 1862 1863 return admissionv1.AdmissionReview{ 1864 Request: &admissionv1.AdmissionRequest{ 1865 Kind: gvk, 1866 Operation: admissionv1.Create, 1867 Object: runtime.RawExtension{ 1868 Raw: raw, 1869 }, 1870 }, 1871 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1872 } 1873 } 1874 1875 // MOCKS SECTION 1876 1877 type fakeGSSListerWithErr struct { 1878 } 1879 1880 // GameServerSetLister interface implementation 1881 func (fgsl *fakeGSSListerWithErr) List(_ labels.Selector) (ret []*v1.GameServerSet, err error) { 1882 return nil, errors.New("random-err") 1883 } 1884 1885 // GameServerSetLister interface implementation 1886 func (fgsl *fakeGSSListerWithErr) Get(_ string) (ret *v1.GameServerSet, err error) { 1887 return nil, errors.New("random-err") 1888 } 1889 1890 func (fgsl *fakeGSSListerWithErr) GameServerSets(_ string) agonesv1client.GameServerSetNamespaceLister { 1891 return fgsl 1892 } 1893 1894 type fakeFleetsGetterWithErr struct{} 1895 1896 // FleetsGetter interface implementation 1897 func (ffg *fakeFleetsGetterWithErr) Fleets(_ string) agonesv1clientset.FleetInterface { 1898 return &fakeFleetsGetterWithErr{} 1899 } 1900 1901 func (ffg *fakeFleetsGetterWithErr) Create(_ context.Context, _ *v1.Fleet, _ metav1.CreateOptions) (*v1.Fleet, error) { 1902 panic("not implemented") 1903 } 1904 1905 func (ffg *fakeFleetsGetterWithErr) Update(_ context.Context, _ *v1.Fleet, _ metav1.UpdateOptions) (*v1.Fleet, error) { 1906 panic("not implemented") 1907 } 1908 1909 func (ffg *fakeFleetsGetterWithErr) UpdateStatus(_ context.Context, _ *v1.Fleet, _ metav1.UpdateOptions) (*v1.Fleet, error) { 1910 panic("not implemented") 1911 } 1912 1913 func (ffg *fakeFleetsGetterWithErr) Delete(_ context.Context, _ string, _ metav1.DeleteOptions) error { 1914 panic("not implemented") 1915 } 1916 1917 func (ffg *fakeFleetsGetterWithErr) DeleteCollection(_ context.Context, _ metav1.DeleteOptions, _ metav1.ListOptions) error { 1918 panic("not implemented") 1919 } 1920 1921 func (ffg *fakeFleetsGetterWithErr) Get(_ context.Context, _ string, _ metav1.GetOptions) (*v1.Fleet, error) { 1922 return nil, errors.New("err-from-fleet-getter") 1923 } 1924 1925 func (ffg *fakeFleetsGetterWithErr) List(_ context.Context, _ metav1.ListOptions) (*v1.FleetList, error) { 1926 panic("not implemented") 1927 } 1928 1929 func (ffg *fakeFleetsGetterWithErr) Watch(_ context.Context, _ metav1.ListOptions) (watch.Interface, error) { 1930 panic("not implemented") 1931 } 1932 1933 func (ffg *fakeFleetsGetterWithErr) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, _ metav1.PatchOptions, _ ...string) (result *v1.Fleet, err error) { 1934 panic("not implemented") 1935 } 1936 1937 func (ffg *fakeFleetsGetterWithErr) Apply(_ context.Context, _ *applyconfigurations.FleetApplyConfiguration, _ metav1.ApplyOptions) (*v1.Fleet, error) { 1938 panic("not implemented") 1939 } 1940 1941 func (ffg *fakeFleetsGetterWithErr) ApplyStatus(_ context.Context, _ *applyconfigurations.FleetApplyConfiguration, _ metav1.ApplyOptions) (*v1.Fleet, error) { 1942 panic("not implemented") 1943 } 1944 1945 func (ffg *fakeFleetsGetterWithErr) GetScale(_ context.Context, _ string, _ metav1.GetOptions) (*autoscalingv1.Scale, error) { 1946 panic("not implemented") 1947 } 1948 1949 func (ffg *fakeFleetsGetterWithErr) UpdateScale(_ context.Context, _ string, _ *autoscalingv1.Scale, _ metav1.UpdateOptions) (*autoscalingv1.Scale, error) { 1950 panic("not implemented") 1951 } 1952 1953 type fakeFleetListerWithErr struct{} 1954 1955 // FleetLister interface implementation 1956 func (ffl *fakeFleetListerWithErr) List(_ labels.Selector) (ret []*v1.Fleet, err error) { 1957 return nil, errors.New("err-from-fleet-lister") 1958 } 1959 1960 func (ffl *fakeFleetListerWithErr) Fleets(_ string) agonesv1client.FleetNamespaceLister { 1961 return &fakeFleetNamespaceListerWithErr{} 1962 } 1963 1964 type fakeFleetNamespaceListerWithErr struct{} 1965 1966 // FleetNamespaceLister interface implementation 1967 func (ffnl *fakeFleetNamespaceListerWithErr) List(_ labels.Selector) (ret []*v1.Fleet, err error) { 1968 return nil, errors.New("err-from-namespace-lister") 1969 } 1970 1971 func (ffnl *fakeFleetNamespaceListerWithErr) Get(_ string) (*v1.Fleet, error) { 1972 return nil, errors.New("err-from-namespace-lister") 1973 }