agones.dev/agones@v1.54.0/test/e2e/fleet_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 e2e 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/sirupsen/logrus" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 appsv1 "k8s.io/api/apps/v1" 30 autoscalingv1 "k8s.io/api/autoscaling/v1" 31 corev1 "k8s.io/api/core/v1" 32 k8serrors "k8s.io/apimachinery/pkg/api/errors" 33 "k8s.io/apimachinery/pkg/api/resource" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/labels" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/apimachinery/pkg/util/intstr" 38 "k8s.io/apimachinery/pkg/util/rand" 39 "k8s.io/apimachinery/pkg/util/validation" 40 "k8s.io/apimachinery/pkg/util/wait" 41 "k8s.io/client-go/util/retry" 42 43 "agones.dev/agones/pkg/apis" 44 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 45 allocationv1 "agones.dev/agones/pkg/apis/allocation/v1" 46 typedagonesv1 "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1" 47 "agones.dev/agones/pkg/util/runtime" 48 e2e "agones.dev/agones/test/e2e/framework" 49 ) 50 51 const ( 52 key = "test-state" 53 red = "red" 54 green = "green" 55 replicasCount = 3 56 ) 57 58 // TestFleetRequestsLimits reproduces an issue when 1000m and 1 CPU is not equal, but should be equal 59 // Every fleet should create no more than 2 GameServerSet at once on a simple fleet patch 60 func TestFleetRequestsLimits(t *testing.T) { 61 t.Parallel() 62 ctx := context.Background() 63 64 flt := defaultFleet(framework.Namespace) 65 flt.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = *resource.NewScaledQuantity(1000, -3) 66 67 client := framework.AgonesClient.AgonesV1() 68 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 69 if assert.NoError(t, err) { 70 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 71 } 72 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 73 74 newReplicas := int32(5) 75 patch := fmt.Sprintf(`[{ "op": "replace", "path": "/spec/template/spec/template/spec/containers/0/resources/requests/cpu", "value": "1000m"}, 76 { "op": "replace", "path": "/spec/replicas", "value": %d}]`, newReplicas) 77 78 _, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Patch(ctx, flt.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{}) 79 require.NoError(t, err) 80 81 // In bug scenario fleet was infinitely creating new GSSets (5 at a time), because 1000m CPU was changed to 1 CPU 82 // internally - thought as new wrong GSSet in a Fleet Controller 83 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(newReplicas)) 84 } 85 86 // TestFleetStrategyValidation reproduces an issue when we are trying 87 // to update a fleet with no strategy in a new one 88 func TestFleetStrategyValidation(t *testing.T) { 89 t.Parallel() 90 ctx := context.Background() 91 92 flt := defaultFleet(framework.Namespace) 93 94 client := framework.AgonesClient.AgonesV1() 95 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 96 require.NoError(t, err) 97 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 98 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 99 100 flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{}) 101 assert.NoError(t, err) 102 // func to check that we receive an expected error 103 verifyErr := func(err error) { 104 assert.NotNil(t, err) 105 statusErr, ok := err.(*k8serrors.StatusError) 106 assert.True(t, ok) 107 fmt.Println(statusErr) 108 assert.Len(t, statusErr.Status().Details.Causes, 1) 109 assert.Equal(t, metav1.CauseTypeFieldValueNotSupported, statusErr.Status().Details.Causes[0].Type) 110 assert.Contains(t, statusErr.Status().Details.Causes[0].Message, `supported values: "RollingUpdate", "Recreate"`) 111 } 112 113 // Change DeploymentStrategy Type, set it to empty string, which is forbidden 114 fltCopy := flt.DeepCopy() 115 fltCopy.Spec.Strategy.Type = appsv1.DeploymentStrategyType("") 116 _, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 117 verifyErr(err) 118 119 // Try to remove whole DeploymentStrategy in a patch 120 patch := `[{ "op": "remove", "path": "/spec/strategy"}, 121 { "op": "replace", "path": "/spec/replicas", "value": 3}]` 122 _, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Patch(ctx, flt.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{}) 123 verifyErr(err) 124 } 125 126 func TestFleetScaleWithDualAllocations(t *testing.T) { 127 t.Parallel() 128 ctx := context.Background() 129 130 client := framework.AgonesClient.AgonesV1() 131 flt := defaultFleet(framework.Namespace) 132 flt.Spec.Replicas = 5 133 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 134 require.NoError(t, err) 135 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 136 137 log := e2e.TestLogger(t).WithField("fleet", flt.Name) 138 139 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 140 _ = framework.CreateAndApplyAllocation(t, flt) 141 142 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 143 return fleet.Status.AllocatedReplicas == 1 144 }) 145 146 err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (done bool, err error) { 147 flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{}) 148 if err != nil { 149 return false, err 150 } 151 152 fltCopy := flt.DeepCopy() 153 fltCopy.Spec.Template.ObjectMeta.Labels = map[string]string{"version": "new"} 154 _, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 155 return true, err 156 }) 157 require.NoError(t, err) 158 159 selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}) 160 require.Eventually(t, func() bool { 161 gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx, 162 metav1.ListOptions{LabelSelector: selector.String()}) 163 if err != nil { 164 log.WithError(err).Error("Should be able to list") 165 return false 166 } 167 168 // wait until there are two of them 169 if len(gssList.Items) < 2 { 170 return false 171 } 172 173 // sort by timestamp, so we have a consistent order. 174 sort.Slice(gssList.Items, func(i, j int) bool { 175 return gssList.Items[i].ObjectMeta.CreationTimestamp.Compare(gssList.Items[j].ObjectMeta.CreationTimestamp.Time) == -1 176 }) 177 178 // First one will have 1 with one allocated, second one should have 9 ready. 179 if gssList.Items[0].Status.AllocatedReplicas != 1 || gssList.Items[1].Status.ReadyReplicas != 4 { 180 log.WithField("gss0", fmt.Sprintf("%+v", gssList.Items[0].Status)).WithField("gss1", fmt.Sprintf("%+v", gssList.Items[1].Status)).Info("Checking GameServerSet") 181 return false 182 } 183 return true 184 }, 5*time.Minute, time.Second) 185 186 // Allocate 2 game servers. 187 _ = framework.CreateAndApplyAllocation(t, flt) 188 _ = framework.CreateAndApplyAllocation(t, flt) 189 190 // Scale the fleet down to 2 replicas. 191 framework.ScaleFleet(t, log, flt, 2) 192 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 193 log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking after 2 more allocations, and scaling to 2") 194 return fleet.Status.AllocatedReplicas == 3 && fleet.Status.ReadyReplicas == 0 195 }) 196 require.NoError(t, err) 197 198 // Then scale the fleet back to 10 replicas. 199 framework.ScaleFleet(t, log, flt, 5) 200 require.NoError(t, err) 201 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 202 log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking after scaling back to 5") 203 return fleet.Status.AllocatedReplicas == 3 && fleet.Status.ReadyReplicas == 2 204 }) 205 } 206 207 func TestFleetScaleUpAllocateEditAndScaleDownToZero(t *testing.T) { 208 t.Parallel() 209 210 ctx := context.Background() 211 212 client := framework.AgonesClient.AgonesV1() 213 214 flt := defaultFleet(framework.Namespace) 215 flt.Spec.Replicas = 1 216 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 217 require.NoError(t, err) 218 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 219 220 assert.Equal(t, int32(1), flt.Spec.Replicas) 221 222 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 223 224 // Scale up to 5 replicas 225 const targetScale = 5 226 flt = scaleFleetPatch(ctx, t, flt, targetScale) 227 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale)) 228 229 // Allocate 1 replica 230 gsa := framework.CreateAndApplyAllocation(t, flt) 231 232 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 233 return fleet.Status.AllocatedReplicas == 1 234 }) 235 236 flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{}) 237 require.NoError(t, err) 238 239 // Edit Port to 6000 240 // Change Port to trigger creating a new GSSet 241 fltCopy := flt.DeepCopy() 242 fltCopy.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{ 243 ContainerPort: 6000, 244 Name: "gameport", 245 PortPolicy: agonesv1.Dynamic, 246 Protocol: corev1.ProtocolUDP, 247 }} 248 249 flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 250 require.NoError(t, err) 251 assert.Equal(t, int32(6000), flt.Spec.Template.Spec.Ports[0].ContainerPort) 252 253 // Wait for one more GSSet to be created 254 err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) { 255 selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}) 256 list, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx, 257 metav1.ListOptions{LabelSelector: selector.String()}) 258 if err != nil { 259 return false, err 260 } 261 ready := false 262 // 2 GSSet should be created 263 if len(list.Items) == 2 { 264 ready = true 265 } 266 return ready, nil 267 }) 268 269 require.NoError(t, err) 270 271 // RollingUpdate has happened due to changing Port, so waiting the complete of the RollingUpdate 272 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 273 return fleet.Status.ReadyReplicas == 4 274 }) 275 276 // Scale down to zero 277 const scaleDownTarget = 0 278 flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget) 279 // Expect Replicas = 0, No GSS or GS 280 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0)) 281 282 // Cleanup the allocated GameServer 283 gp := int64(1) 284 err = client.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp}) 285 require.NoError(t, err) 286 287 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0)) 288 289 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 290 return fleet.Status.AllocatedReplicas == 0 291 }) 292 293 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 294 return fleet.Status.Replicas == 0 295 }) 296 297 } 298 299 func TestFleetScaleUpEditAndScaleDown(t *testing.T) { 300 t.Parallel() 301 302 // Use scaleFleetPatch (true) or scaleFleetSubresource (false) 303 fixtures := []bool{true, false} 304 305 for _, usePatch := range fixtures { 306 t.Run("Use fleet Patch "+fmt.Sprint(usePatch), func(t *testing.T) { 307 t.Parallel() 308 ctx := context.Background() 309 310 client := framework.AgonesClient.AgonesV1() 311 312 flt := defaultFleet(framework.Namespace) 313 flt.Spec.Replicas = 1 314 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 315 require.NoError(t, err) 316 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 317 318 assert.Equal(t, int32(1), flt.Spec.Replicas) 319 320 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 321 322 // scale up 323 const targetScale = 3 324 if usePatch { 325 flt = scaleFleetPatch(ctx, t, flt, targetScale) 326 assert.Equal(t, int32(targetScale), flt.Spec.Replicas) 327 } else { 328 flt = scaleFleetSubresource(ctx, t, flt, targetScale) 329 } 330 331 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale)) 332 gsa := framework.CreateAndApplyAllocation(t, flt) 333 334 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 335 return fleet.Status.AllocatedReplicas == 1 336 }) 337 338 flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{}) 339 require.NoError(t, err) 340 341 // Change ContainerPort to trigger creating a new GSSet 342 fltCopy := flt.DeepCopy() 343 fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++ 344 flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 345 require.NoError(t, err) 346 347 // Wait for one more GSSet to be created and ReadyReplicas created in new GSS 348 err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 349 selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}) 350 list, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx, 351 metav1.ListOptions{LabelSelector: selector.String()}) 352 if err != nil { 353 return false, err 354 } 355 ready := false 356 if len(list.Items) == 2 { 357 for _, v := range list.Items { 358 if v.Status.ReadyReplicas > 0 && v.Status.AllocatedReplicas == 0 { 359 ready = true 360 } 361 } 362 } 363 return ready, nil 364 }) 365 366 require.NoError(t, err) 367 368 // scale down, with allocation 369 const scaleDownTarget = 1 370 if usePatch { 371 flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget) 372 } else { 373 flt = scaleFleetSubresource(ctx, t, flt, scaleDownTarget) 374 } 375 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0)) 376 377 // delete the allocated GameServer 378 gp := int64(1) 379 err = client.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp}) 380 require.NoError(t, err) 381 382 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1)) 383 384 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 385 return fleet.Status.AllocatedReplicas == 0 386 }) 387 }) 388 } 389 } 390 391 // TestFleetRollingUpdate - test that the limited number of gameservers are created and deleted at a time 392 // maxUnavailable and maxSurge parameters check. 393 func TestFleetRollingUpdate(t *testing.T) { 394 t.Parallel() 395 ctx := context.Background() 396 fixtures := []struct { 397 // Use scaleFleetPatch (true) or scaleFleetSubresource (false) 398 usePatch bool 399 maxSurge string 400 // If true, create and cycle GS Allocations when triggering a rolling update: 401 // - Repeatedly allocate and shutdown GameServers (keeping ~50% of the Fleet in an Allocated state). 402 // - Once 50% of the Fleet is Allocated, trigger the rolling update. 403 // - Keep allocating/shutting down GameServers, to allocate in both the old and new GSSets. 404 // - Verify the rolling update completes. 405 // This simulates updating a Fleet that is live/in-use, and reproduces an issue where allocated GameServers 406 // causes a rolling update to get stuck and keep the old GameServerSet up. 407 cycleAllocations bool 408 }{ 409 { 410 usePatch: true, 411 maxSurge: "25%", 412 cycleAllocations: false, 413 }, 414 { 415 usePatch: true, 416 maxSurge: "10%", 417 cycleAllocations: false, 418 }, 419 { 420 usePatch: false, 421 maxSurge: "25%", 422 cycleAllocations: false, 423 }, 424 { 425 usePatch: false, 426 maxSurge: "10%", 427 cycleAllocations: false, 428 }, 429 { 430 usePatch: true, 431 maxSurge: "25%", 432 cycleAllocations: true, 433 }, 434 } 435 for i := range fixtures { 436 fixture := fixtures[i] 437 t.Run(fmt.Sprintf("Use fleet Patch %t %s cycle %t", fixture.usePatch, fixture.maxSurge, fixture.cycleAllocations), func(t *testing.T) { 438 client := framework.AgonesClient.AgonesV1() 439 440 flt := defaultFleet(framework.Namespace) 441 flt.ApplyDefaults() 442 flt.Spec.Replicas = 1 443 rollingUpdatePercent := intstr.FromString(fixture.maxSurge) 444 flt.Spec.Strategy.RollingUpdate.MaxSurge = &rollingUpdatePercent 445 flt.Spec.Strategy.RollingUpdate.MaxUnavailable = &rollingUpdatePercent 446 log := e2e.TestLogger(t).WithField("fleet", flt.ObjectMeta.Name) 447 448 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 449 require.NoError(t, err) 450 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 451 452 assert.Equal(t, int32(1), flt.Spec.Replicas) 453 assert.Equal(t, fixture.maxSurge, flt.Spec.Strategy.RollingUpdate.MaxSurge.StrVal) 454 assert.Equal(t, fixture.maxSurge, flt.Spec.Strategy.RollingUpdate.MaxUnavailable.StrVal) 455 456 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 457 458 // scale up 459 const targetScale = 8 460 if fixture.usePatch { 461 flt = scaleFleetPatch(ctx, t, flt, targetScale) 462 assert.Equal(t, int32(targetScale), flt.Spec.Replicas) 463 } else { 464 flt = scaleFleetSubresource(ctx, t, flt, targetScale) 465 } 466 467 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale)) 468 469 flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{}) 470 require.NoError(t, err) 471 472 cycleCtx, cancelCycle := context.WithCancel(ctx) 473 defer cancelCycle() 474 if fixture.cycleAllocations { 475 // Repeatedly cycle allocations to keep ~half of the GameServers Allocated. Repeatedly Allocate and 476 // delete such that both the old and new GSSet contain allocated GameServers. 477 const halfScale = targetScale / 2 478 const period = 3 * time.Second 479 go framework.CycleAllocations(cycleCtx, t, flt, period, period*halfScale) 480 481 // Wait for at least half of the fleet to have be cycled (either Allocated or shutting down) 482 // before updating the fleet. 483 err = framework.WaitForFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 484 return fleet.Status.ReadyReplicas < halfScale 485 }) 486 } 487 488 // Change ContainerPort to trigger creating a new GSSet. Retry in case of a conflict. 489 fltName := flt.GetName() 490 require.Eventually(t, func() bool { 491 flt, err = client.Fleets(framework.Namespace).Get(ctx, fltName, metav1.GetOptions{}) 492 require.NoError(t, err) 493 fltCopy := flt.DeepCopy() 494 fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++ 495 flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 496 if err != nil { 497 log.WithError(err).Info("Failed to update Fleet") 498 } 499 return err == nil 500 }, time.Minute, time.Second) 501 502 selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}) 503 // New GSS was created 504 err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) { 505 gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx, 506 metav1.ListOptions{LabelSelector: selector.String()}) 507 if err != nil { 508 return false, err 509 } 510 return len(gssList.Items) == 2, nil 511 }) 512 assert.NoError(t, err) 513 // Check that total number of gameservers in the system does not exceed the RollingUpdate 514 // parameters (creating no more than maxSurge, deleting maxUnavailable servers at a time) 515 // Wait for old GSSet to be deleted 516 err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { 517 list, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).List(ctx, 518 metav1.ListOptions{LabelSelector: selector.String()}) 519 if err != nil { 520 return false, err 521 } 522 523 maxSurge, err := intstr.GetScaledValueFromIntOrPercent(flt.Spec.Strategy.RollingUpdate.MaxSurge, int(flt.Spec.Replicas), true) 524 require.NoError(t, err) 525 526 roundUp := false 527 maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(flt.Spec.Strategy.RollingUpdate.MaxUnavailable, int(flt.Spec.Replicas), roundUp) 528 529 if maxUnavailable == 0 { 530 maxUnavailable = 1 531 } 532 // This difference is inevitable, also could be seen with Deployments and ReplicaSets 533 shift := maxUnavailable 534 require.NoError(t, err) 535 536 // Ignore any GameServers that are shutting down (resulting from Allocation cycling). 537 shuttingDown := 0 538 for _, gs := range list.Items { 539 if gs.IsBeingDeleted() { 540 shuttingDown++ 541 } 542 } 543 expectedTotal := targetScale + maxSurge + maxUnavailable + shift + shuttingDown 544 if len(list.Items) > expectedTotal { 545 err = fmt.Errorf("new replicas should be less than target + maxSurge + maxUnavailable + shift + shuttingDown. Replicas: %d, Expected: %d", len(list.Items), expectedTotal) 546 } 547 if err != nil { 548 return false, err 549 } 550 gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx, 551 metav1.ListOptions{LabelSelector: selector.String()}) 552 if err != nil { 553 return false, err 554 } 555 return len(gssList.Items) == 1, nil 556 }) 557 558 assert.NoError(t, err) 559 560 // Stop cycling Allocations. 561 // The AssertFleetConditions below will wait until the Allocation cycling has 562 // fully stopped (when all Allocated GameServers are shut down). 563 cancelCycle() 564 565 // scale down, with allocation 566 const scaleDownTarget = 1 567 if fixture.usePatch { 568 flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget) 569 } else { 570 flt = scaleFleetSubresource(ctx, t, flt, scaleDownTarget) 571 } 572 573 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1)) 574 575 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 576 return fleet.Status.AllocatedReplicas == 0 577 }) 578 }) 579 } 580 } 581 582 func TestUpdateFleetReplicaAndSpec(t *testing.T) { 583 t.Parallel() 584 585 client := framework.AgonesClient.AgonesV1() 586 ctx := context.Background() 587 588 flt := defaultFleet(framework.Namespace) 589 flt.ApplyDefaults() 590 591 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 592 require.NoError(t, err) 593 594 logrus.WithField("fleet", flt).Info("Created Fleet") 595 596 selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}) 597 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 598 599 require.Eventuallyf(t, func() bool { 600 list, err := client.GameServerSets(framework.Namespace).List(ctx, 601 metav1.ListOptions{LabelSelector: selector.String()}) 602 require.NoError(t, err) 603 return len(list.Items) == 1 604 }, time.Minute, time.Second, "Wrong number of GameServerSets") 605 606 // update both replicas and template at the same time 607 608 flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{}) 609 require.NoError(t, err) 610 fltCopy := flt.DeepCopy() 611 fltCopy.Spec.Replicas = 0 612 fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++ 613 require.NotEqual(t, flt.Spec.Template.Spec.Ports[0].ContainerPort, fltCopy.Spec.Template.Spec.Ports[0].ContainerPort) 614 615 flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 616 require.NoError(t, err) 617 require.Empty(t, flt.Spec.Replicas) 618 619 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 620 621 require.Eventuallyf(t, func() bool { 622 list, err := client.GameServerSets(framework.Namespace).List(ctx, 623 metav1.ListOptions{LabelSelector: selector.String()}) 624 require.NoError(t, err) 625 return len(list.Items) == 1 && list.Items[0].Spec.Replicas == 0 626 }, time.Minute, time.Second, "Wrong number of GameServerSets") 627 } 628 629 func TestScaleFleetUpAndDownWithGameServerAllocation(t *testing.T) { 630 t.Parallel() 631 ctx := context.Background() 632 fixtures := []bool{false, true} 633 634 for _, usePatch := range fixtures { 635 t.Run("Use fleet Patch "+fmt.Sprint(usePatch), func(t *testing.T) { 636 t.Parallel() 637 638 client := framework.AgonesClient.AgonesV1() 639 640 flt := defaultFleet(framework.Namespace) 641 flt.Spec.Replicas = 1 642 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 643 require.NoError(t, err) 644 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 645 646 assert.Equal(t, int32(1), flt.Spec.Replicas) 647 648 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 649 650 // scale up 651 const targetScale = 3 652 if usePatch { 653 flt = scaleFleetPatch(ctx, t, flt, targetScale) 654 assert.Equal(t, int32(targetScale), flt.Spec.Replicas) 655 } else { 656 flt = scaleFleetSubresource(ctx, t, flt, targetScale) 657 } 658 659 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale)) 660 661 // get an allocation 662 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 663 Spec: allocationv1.GameServerAllocationSpec{ 664 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}, 665 }} 666 667 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 668 require.NoError(t, err) 669 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa.Status.State) 670 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 671 return fleet.Status.AllocatedReplicas == 1 672 }) 673 674 // scale down, with allocation 675 const scaleDownTarget = 1 676 if usePatch { 677 flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget) 678 } else { 679 flt = scaleFleetSubresource(ctx, t, flt, scaleDownTarget) 680 } 681 682 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0)) 683 684 // delete the allocated GameServer 685 gp := int64(1) 686 err = client.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp}) 687 require.NoError(t, err) 688 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1)) 689 690 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 691 return fleet.Status.AllocatedReplicas == 0 692 }) 693 }) 694 } 695 } 696 697 func TestFleetUpdates(t *testing.T) { 698 t.Parallel() 699 ctx := context.Background() 700 701 fixtures := map[string]func() *agonesv1.Fleet{ 702 "recreate": func() *agonesv1.Fleet { 703 flt := defaultFleet(framework.Namespace) 704 flt.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType 705 return flt 706 }, 707 "rolling": func() *agonesv1.Fleet { 708 flt := defaultFleet(framework.Namespace) 709 flt.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType 710 return flt 711 }, 712 } 713 714 for k, v := range fixtures { 715 t.Run(k, func(t *testing.T) { 716 t.Parallel() 717 client := framework.AgonesClient.AgonesV1() 718 log := e2e.TestLogger(t) 719 720 flt := v() 721 flt.Spec.Template.ObjectMeta.Annotations = map[string]string{key: red} 722 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 723 require.NoError(t, err) 724 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 725 726 // gate that we have the keys we expect. 727 err = framework.WaitForFleetGameServersCondition(flt, func(gs *agonesv1.GameServer) bool { 728 return gs.ObjectMeta.Annotations[key] == red 729 }) 730 require.NoError(t, err) 731 732 // if the generation has been updated, it's time to try again. 733 err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 734 flt, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{}) 735 if err != nil { 736 log.WithError(err).WithField("flt", flt.ObjectMeta.Name).Warn("Could not retrieve fleet, trying again") 737 return false, err 738 } 739 fltCopy := flt.DeepCopy() 740 fltCopy.Spec.Template.ObjectMeta.Annotations[key] = green 741 _, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 742 if err != nil { 743 log.WithError(err).WithField("flt", flt.ObjectMeta.Name).Warn("Could not update fleet, trying again") 744 return false, nil 745 } 746 747 return true, nil 748 }) 749 require.NoError(t, err) 750 751 // let's make sure we're fully Ready 752 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 753 return flt.Spec.Replicas == fleet.Status.ReadyReplicas 754 }) 755 756 // ...and fully rolled out 757 err = framework.WaitForFleetGameServersCondition(flt, func(gs *agonesv1.GameServer) bool { 758 return gs.ObjectMeta.Annotations[key] == green 759 }) 760 require.NoError(t, err) 761 }) 762 } 763 } 764 765 func TestUpdateGameServerConfigurationInFleet(t *testing.T) { 766 t.Parallel() 767 ctx := context.Background() 768 769 client := framework.AgonesClient.AgonesV1() 770 771 gsSpec := framework.DefaultGameServer(framework.Namespace).Spec 772 oldPort := int32(7111) 773 gsSpec.Ports = []agonesv1.GameServerPort{{ 774 ContainerPort: oldPort, 775 Name: "gameport", 776 PortPolicy: agonesv1.Dynamic, 777 Protocol: corev1.ProtocolUDP, 778 }} 779 flt := fleetWithGameServerSpec(&gsSpec, framework.Namespace) 780 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 781 require.NoError(t, err) 782 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 783 784 assert.Equal(t, int32(replicasCount), flt.Spec.Replicas) 785 786 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 787 788 // get an allocation 789 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 790 Spec: allocationv1.GameServerAllocationSpec{ 791 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}, 792 }} 793 794 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 795 require.NoError(t, err) 796 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa.Status.State) 797 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 798 return fleet.Status.AllocatedReplicas == 1 799 }) 800 801 flt, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.Name, metav1.GetOptions{}) 802 require.NoError(t, err) 803 804 // Update the configuration of the gameservers of the fleet, i.e. container port. 805 // The changes should only be rolled out to gameservers in ready state, but not the allocated gameserver. 806 newPort := int32(7222) 807 fltCopy := flt.DeepCopy() 808 fltCopy.Spec.Template.Spec.Ports[0].ContainerPort = newPort 809 810 _, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 811 require.NoError(t, err) 812 813 err = framework.WaitForFleetGameServersCondition(flt, func(gs *agonesv1.GameServer) bool { 814 containerPort := gs.Spec.Ports[0].ContainerPort 815 return (gs.Name == gsa.Status.GameServerName && containerPort == oldPort) || 816 (gs.Name != gsa.Status.GameServerName && containerPort == newPort) 817 }) 818 require.NoError(t, err) 819 } 820 821 func TestReservedGameServerInFleet(t *testing.T) { 822 t.Parallel() 823 ctx := context.Background() 824 825 client := framework.AgonesClient.AgonesV1() 826 827 flt := defaultFleet(framework.Namespace) 828 flt.Spec.Replicas = 3 829 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 830 if assert.NoError(t, err) { 831 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 832 } 833 834 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 835 836 gsList, err := framework.ListGameServersFromFleet(flt) 837 assert.NoError(t, err) 838 839 assert.Len(t, gsList, int(flt.Spec.Replicas)) 840 841 // mark one as reserved 842 gsCopy := gsList[0].DeepCopy() 843 gsCopy.Status.State = agonesv1.GameServerStateReserved 844 _, err = client.GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{}) 845 assert.NoError(t, err) 846 847 // make sure counts are correct 848 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 849 return fleet.Status.ReadyReplicas == 2 && fleet.Status.ReservedReplicas == 1 850 }) 851 852 // scale down to 0 853 flt = scaleFleetSubresource(ctx, t, flt, 0) 854 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0)) 855 856 // one should be left behind 857 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 858 result := fleet.Status.ReservedReplicas == 1 859 log.WithField("reserved", fleet.Status.ReservedReplicas).WithField("result", result).Info("waiting for 1 reserved replica") 860 return result 861 }) 862 863 // check against gameservers directly too, just to be extra sure 864 err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(_ context.Context) (done bool, err error) { 865 list, err := framework.ListGameServersFromFleet(flt) 866 if err != nil { 867 return true, err 868 } 869 l := len(list) 870 e := logrus.WithField("len", l) 871 if l >= 1 { 872 e = e.WithField("state", list[0].Status.State) 873 } 874 e.Info("waiting for 1 reserved gs") 875 return l == 1 && list[0].Status.State == agonesv1.GameServerStateReserved, nil 876 }) 877 assert.NoError(t, err) 878 } 879 880 // TestFleetGSSpecValidation is built to test Fleet's underlying Gameserver template 881 // validation. Gameserver Spec contained in a Fleet should be valid to create a fleet. 882 func TestFleetGSSpecValidation(t *testing.T) { 883 t.Parallel() 884 ctx := context.Background() 885 client := framework.AgonesClient.AgonesV1() 886 887 // check two Containers in Gameserver Spec Template validation 888 flt := defaultFleet(framework.Namespace) 889 containerName := "container2" 890 flt.Spec.Template.Spec.Template = 891 corev1.PodTemplateSpec{ 892 Spec: corev1.PodSpec{ 893 Containers: []corev1.Container{{Name: "container", Image: "myImage"}, {Name: containerName, Image: "myImage2"}}, 894 }, 895 } 896 flt.Spec.Template.Spec.Container = "testing" 897 _, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 898 assert.NotNil(t, err) 899 statusErr, ok := err.(*k8serrors.StatusError) 900 assert.True(t, ok) 901 902 assert.Len(t, statusErr.Status().Details.Causes, 2) 903 assert.Contains(t, statusErr.Status().Details.Causes[1].Message, "Container must be empty or the name of a container in the pod template") 904 905 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 906 assert.Contains(t, statusErr.Status().Details.Causes[0].Message, "Could not find a container named testing") 907 908 flt.Spec.Template.Spec.Container = "" 909 _, err = client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 910 assert.NotNil(t, err) 911 statusErr, ok = err.(*k8serrors.StatusError) 912 assert.True(t, ok) 913 if assert.Len(t, statusErr.Status().Details.Causes, 2) { 914 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[1].Type) 915 assert.Contains(t, statusErr.Status().Details.Causes[1].Message, "Could not find a container named ") 916 } 917 assert.Equal(t, metav1.CauseTypeFieldValueRequired, statusErr.Status().Details.Causes[0].Type) 918 assert.Contains(t, statusErr.Status().Details.Causes[0].Message, agonesv1.ErrContainerRequired) 919 920 // use valid name for a container, one of two defined above 921 flt.Spec.Template.Spec.Container = containerName 922 _, err = client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 923 require.NoError(t, err) 924 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 925 926 // check port configuration validation 927 fltPort := defaultFleet(framework.Namespace) 928 929 fltPort.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{Name: "Dyn", HostPort: 5555, PortPolicy: agonesv1.Dynamic, ContainerPort: 5555}} 930 931 _, err = client.Fleets(framework.Namespace).Create(ctx, fltPort, metav1.CreateOptions{}) 932 assert.NotNil(t, err) 933 statusErr, ok = err.(*k8serrors.StatusError) 934 assert.True(t, ok) 935 assert.Len(t, statusErr.Status().Details.Causes, 1) 936 assert.Contains(t, statusErr.Status().Details.Causes[0].Message, agonesv1.ErrHostPort) 937 938 fltPort.Spec.Template.Spec.Ports[0].HostPort = 0 // validation fails above because the HostPort is specified, make it good. 939 _, err = client.Fleets(framework.Namespace).Create(ctx, fltPort, metav1.CreateOptions{}) 940 require.NoError(t, err) 941 defer client.Fleets(framework.Namespace).Delete(ctx, fltPort.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 942 } 943 944 // TestFleetNameValidation is built to test Fleet Name length validation, 945 // Fleet Name should have at most 63 chars. 946 func TestFleetNameValidation(t *testing.T) { 947 t.Parallel() 948 ctx := context.Background() 949 client := framework.AgonesClient.AgonesV1() 950 951 flt := defaultFleet(framework.Namespace) 952 nameLen := validation.LabelValueMaxLength + 1 953 flt.Name = strings.Repeat("f", nameLen) 954 _, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 955 require.NotNil(t, err) 956 statusErr := err.(*k8serrors.StatusError) 957 assert.True(t, len(statusErr.Status().Details.Causes) > 0) 958 assert.Equal(t, metav1.CauseType("FieldValueTooLong"), statusErr.Status().Details.Causes[0].Type) 959 goodFlt := defaultFleet(framework.Namespace) 960 goodFlt.Name = flt.Name[0 : nameLen-1] 961 goodFlt, err = client.Fleets(framework.Namespace).Create(ctx, goodFlt, metav1.CreateOptions{}) 962 require.NoError(t, err) 963 defer client.Fleets(framework.Namespace).Delete(ctx, goodFlt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 964 } 965 966 func assertSuccessOrUpdateConflict(t *testing.T, err error) { 967 if !k8serrors.IsConflict(err) { 968 // update conflicts are sometimes ok, we simply lost the race. 969 require.NoError(t, err) 970 } 971 } 972 973 // TestGameServerAllocationDuringGameServerDeletion is built to specifically 974 // test for race conditions of allocations when doing scale up/down, 975 // rolling updates, etc. Failures may not happen ALL the time -- as that is the 976 // nature of race conditions. 977 func TestGameServerAllocationDuringGameServerDeletion(t *testing.T) { 978 t.Parallel() 979 ctx := context.Background() 980 981 testAllocationRaceCondition := func(t *testing.T, fleet func(string) *agonesv1.Fleet, deltaSleep time.Duration, delta func(t *testing.T, flt *agonesv1.Fleet)) { 982 client := framework.AgonesClient.AgonesV1() 983 984 flt := fleet(framework.Namespace) 985 flt.ApplyDefaults() 986 size := int32(10) 987 flt.Spec.Replicas = size 988 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 989 require.NoError(t, err) 990 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 991 992 assert.Equal(t, size, flt.Spec.Replicas) 993 994 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 995 996 var allocs []string 997 998 wg := sync.WaitGroup{} 999 wg.Add(2) 1000 go func() { 1001 for { 1002 // this gives room for fleet scaling to go down - makes it more likely for the race condition to fire 1003 time.Sleep(100 * time.Millisecond) 1004 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 1005 Spec: allocationv1.GameServerAllocationSpec{ 1006 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}, 1007 }} 1008 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 1009 if err != nil || gsa.Status.State == allocationv1.GameServerAllocationUnAllocated { 1010 logrus.WithError(err).Info("Allocation ended") 1011 break 1012 } 1013 logrus.WithField("gs", gsa.Status.GameServerName).Info("Allocated") 1014 allocs = append(allocs, gsa.Status.GameServerName) 1015 } 1016 wg.Done() 1017 }() 1018 go func() { 1019 // this tends to force the scaling to happen as we are fleet allocating 1020 time.Sleep(deltaSleep) 1021 // call the function that makes the change to the fleet 1022 logrus.Info("Applying delta function") 1023 delta(t, flt) 1024 wg.Done() 1025 }() 1026 1027 wg.Wait() 1028 assert.NotEmpty(t, allocs) 1029 1030 for _, name := range allocs { 1031 gsCheck, err := client.GameServers(framework.Namespace).Get(ctx, name, metav1.GetOptions{}) 1032 require.NoError(t, err) 1033 assert.True(t, gsCheck.ObjectMeta.DeletionTimestamp.IsZero()) 1034 } 1035 } 1036 1037 t.Run("scale down", func(t *testing.T) { 1038 t.Parallel() 1039 1040 testAllocationRaceCondition(t, defaultFleet, time.Second, 1041 func(t *testing.T, flt *agonesv1.Fleet) { 1042 const targetScale = int32(0) 1043 flt = scaleFleetPatch(ctx, t, flt, targetScale) 1044 assert.Equal(t, targetScale, flt.Spec.Replicas) 1045 }) 1046 }) 1047 1048 t.Run("recreate update", func(t *testing.T) { 1049 t.Parallel() 1050 1051 fleet := func(ns string) *agonesv1.Fleet { 1052 flt := defaultFleet(ns) 1053 flt.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType 1054 flt.Spec.Template.ObjectMeta.Annotations = map[string]string{key: red} 1055 1056 return flt 1057 } 1058 1059 testAllocationRaceCondition(t, fleet, time.Second, 1060 func(t *testing.T, flt *agonesv1.Fleet) { 1061 flt, err := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{}) 1062 require.NoError(t, err) 1063 fltCopy := flt.DeepCopy() 1064 fltCopy.Spec.Template.ObjectMeta.Annotations[key] = green 1065 _, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 1066 assertSuccessOrUpdateConflict(t, err) 1067 }) 1068 }) 1069 1070 t.Run("rolling update", func(t *testing.T) { 1071 t.Parallel() 1072 1073 fleet := func(ns string) *agonesv1.Fleet { 1074 flt := defaultFleet(ns) 1075 flt.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType 1076 flt.Spec.Template.ObjectMeta.Annotations = map[string]string{key: red} 1077 1078 return flt 1079 } 1080 1081 testAllocationRaceCondition(t, fleet, time.Duration(0), 1082 func(t *testing.T, flt *agonesv1.Fleet) { 1083 flt, err := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{}) 1084 require.NoError(t, err) 1085 fltCopy := flt.DeepCopy() 1086 fltCopy.Spec.Template.ObjectMeta.Annotations[key] = green 1087 _, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{}) 1088 assertSuccessOrUpdateConflict(t, err) 1089 }) 1090 }) 1091 } 1092 1093 // TestCreateFleetAndUpdateScaleSubresource is built to 1094 // test scale subresource usage and its ability to change Fleet Replica size. 1095 // Both scaling up and down. 1096 func TestCreateFleetAndUpdateScaleSubresource(t *testing.T) { 1097 t.Parallel() 1098 ctx := context.Background() 1099 1100 client := framework.AgonesClient.AgonesV1() 1101 1102 flt := defaultFleet(framework.Namespace) 1103 const initialReplicas int32 = 1 1104 flt.Spec.Replicas = initialReplicas 1105 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1106 require.NoError(t, err) 1107 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1108 assert.Equal(t, initialReplicas, flt.Spec.Replicas) 1109 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1110 1111 newReplicas := initialReplicas * 2 1112 scaleFleetSubresource(ctx, t, flt, newReplicas) 1113 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(newReplicas)) 1114 1115 scaleFleetSubresource(ctx, t, flt, initialReplicas) 1116 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(initialReplicas)) 1117 } 1118 1119 // TestScaleUpAndDownInParallelStressTest creates N fleets, half of which start with replicas=0 1120 // and the other half with 0 and scales them up/down 3 times in parallel expecting it to reach 1121 // the desired number of ready replicas each time. 1122 // This test is also used as a stress test with 'make stress-test-e2e', in which case it creates 1123 // many more fleets of bigger sizes and runs many more repetitions. 1124 func TestScaleUpAndDownInParallelStressTest(t *testing.T) { 1125 t.Parallel() 1126 ctx := context.Background() 1127 1128 client := framework.AgonesClient.AgonesV1() 1129 fleetCount := 2 1130 fleetSize := int32(10) 1131 defaultReplicas := int32(1) 1132 repeatCount := 3 1133 deadline := time.Now().Add(1 * time.Minute) 1134 1135 logrus.WithField("fleetCount", fleetCount). 1136 WithField("fleetSize", fleetSize). 1137 WithField("repeatCount", repeatCount). 1138 WithField("deadline", deadline). 1139 Info("starting scale up/down test") 1140 1141 if framework.StressTestLevel > 0 { 1142 fleetSize = 10 * int32(framework.StressTestLevel) 1143 repeatCount = 10 1144 fleetCount = 10 1145 deadline = time.Now().Add(45 * time.Minute) 1146 } 1147 1148 var fleets []*agonesv1.Fleet 1149 1150 scaleUpStats := framework.NewStatsCollector(fmt.Sprintf("fleet_%v_scale_up", fleetSize), framework.Version) 1151 scaleDownStats := framework.NewStatsCollector(fmt.Sprintf("fleet_%v_scale_down", fleetSize), framework.Version) 1152 1153 defer scaleUpStats.Report() 1154 defer scaleDownStats.Report() 1155 1156 for fleetNumber := 0; fleetNumber < fleetCount; fleetNumber++ { 1157 flt := defaultFleet(framework.Namespace) 1158 flt.ObjectMeta.GenerateName = fmt.Sprintf("scale-fleet-%v-", fleetNumber) 1159 if fleetNumber%2 == 0 { 1160 // even-numbered fleets starts at fleetSize and are scaled down to zero and back. 1161 flt.Spec.Replicas = fleetSize 1162 } else { 1163 // odd-numbered fleets starts at default 1 replica and are scaled up to fleetSize and back. 1164 flt.Spec.Replicas = defaultReplicas 1165 } 1166 1167 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1168 require.NoError(t, err) 1169 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint 1170 fleets = append(fleets, flt) 1171 } 1172 1173 // wait for initial fleet conditions. 1174 for fleetNumber, flt := range fleets { 1175 if fleetNumber%2 == 0 { 1176 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(fleetSize)) 1177 } else { 1178 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(defaultReplicas)) 1179 } 1180 } 1181 errorsChan := make(chan error) 1182 var wg sync.WaitGroup 1183 finished := make(chan bool, 1) 1184 1185 for fleetNumber, flt := range fleets { 1186 wg.Add(1) 1187 go func(fleetNumber int, flt *agonesv1.Fleet) { 1188 defer wg.Done() 1189 defer func() { 1190 if err := recover(); err != nil { 1191 t.Errorf("recovered panic: %v", err) 1192 } 1193 }() 1194 1195 if fleetNumber%2 == 0 { 1196 duration, err := scaleAndWait(ctx, t, flt, 0) 1197 if err != nil { 1198 fmt.Println(err) 1199 errorsChan <- err 1200 return 1201 } 1202 scaleDownStats.ReportDuration(duration, nil) 1203 } 1204 for i := 0; i < repeatCount; i++ { 1205 if time.Now().After(deadline) { 1206 break 1207 } 1208 duration, err := scaleAndWait(ctx, t, flt, fleetSize) 1209 if err != nil { 1210 fmt.Println(err) 1211 errorsChan <- err 1212 return 1213 } 1214 scaleUpStats.ReportDuration(duration, nil) 1215 duration, err = scaleAndWait(ctx, t, flt, 0) 1216 if err != nil { 1217 fmt.Println(err) 1218 errorsChan <- err 1219 return 1220 } 1221 scaleDownStats.ReportDuration(duration, nil) 1222 } 1223 }(fleetNumber, flt) 1224 } 1225 go func() { 1226 wg.Wait() 1227 close(finished) 1228 }() 1229 1230 select { 1231 case <-finished: 1232 case err := <-errorsChan: 1233 t.Fatalf("Error in waiting for a fleet to scale: %s", err) 1234 } 1235 fmt.Println("We are Done") 1236 } 1237 1238 // Creates a fleet and one GameServer with Packed scheduling. 1239 // Scale to two GameServers with Distributed scheduling. 1240 // The old GameServer has Scheduling set to 5 and the new one has it set to Distributed. 1241 func TestUpdateFleetScheduling(t *testing.T) { 1242 t.Parallel() 1243 ctx := context.Background() 1244 1245 t.Run("Updating Spec.Scheduling on fleet should be updated in GameServer", 1246 func(t *testing.T) { 1247 framework.SkipOnCloudProduct(t, "gke-autopilot", "Autopilot does not support Distributed scheduling") 1248 client := framework.AgonesClient.AgonesV1() 1249 1250 flt := defaultFleet(framework.Namespace) 1251 flt.Spec.Replicas = 1 1252 flt.Spec.Scheduling = apis.Packed 1253 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1254 1255 require.NoError(t, err) 1256 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1257 1258 assert.Equal(t, int32(1), flt.Spec.Replicas) 1259 assert.Equal(t, apis.Packed, flt.Spec.Scheduling) 1260 1261 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1262 1263 const targetScale = 2 1264 flt = schedulingFleetPatch(ctx, t, flt, apis.Distributed, targetScale) 1265 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale)) 1266 1267 assert.Equal(t, int32(targetScale), flt.Spec.Replicas) 1268 assert.Equal(t, apis.Distributed, flt.Spec.Scheduling) 1269 1270 err = framework.WaitForFleetGameServerListCondition(flt, 1271 func(gsList []agonesv1.GameServer) bool { 1272 return countFleetScheduling(gsList, apis.Distributed) == 1 && 1273 countFleetScheduling(gsList, apis.Packed) == 1 1274 }) 1275 require.NoError(t, err) 1276 }) 1277 } 1278 1279 // TestFleetWithZeroReplicas ensures that we can always create 0 replica 1280 // fleets, which is useful! 1281 func TestFleetWithZeroReplicas(t *testing.T) { 1282 t.Parallel() 1283 ctx := context.Background() 1284 client := framework.AgonesClient.AgonesV1() 1285 1286 flt := defaultFleet(framework.Namespace) 1287 flt.Spec.Replicas = 0 1288 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1289 assert.NoError(t, err) 1290 1291 // can't think of a better way to wait for a bit before checking. 1292 time.Sleep(time.Second) 1293 1294 list, err := framework.ListGameServersFromFleet(flt) 1295 assert.NoError(t, err) 1296 assert.Empty(t, list) 1297 } 1298 1299 // TestFleetWithLongLabelsAnnotations ensures that we can not create a fleet 1300 // with label over 64 chars and Annotations key over 64 1301 func TestFleetWithLongLabelsAnnotations(t *testing.T) { 1302 t.Parallel() 1303 ctx := context.Background() 1304 1305 client := framework.AgonesClient.AgonesV1() 1306 fleetSize := int32(1) 1307 flt := defaultFleet(framework.Namespace) 1308 flt.Spec.Replicas = fleetSize 1309 normalLengthName := strings.Repeat("f", validation.LabelValueMaxLength) 1310 longName := normalLengthName + "f" 1311 flt.Spec.Template.ObjectMeta.Labels = make(map[string]string) 1312 flt.Spec.Template.ObjectMeta.Labels["label"] = longName 1313 _, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1314 assert.Error(t, err) 1315 statusErr, ok := err.(*k8serrors.StatusError) 1316 assert.True(t, ok) 1317 assert.Len(t, statusErr.Status().Details.Causes, 1) 1318 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1319 assert.Equal(t, "spec.template.metadata.labels", statusErr.Status().Details.Causes[0].Field) 1320 1321 // Set Label to normal size and add Annotations with an error 1322 flt.Spec.Template.ObjectMeta.Labels["label"] = normalLengthName 1323 flt.Spec.Template.ObjectMeta.Annotations = make(map[string]string) 1324 flt.Spec.Template.ObjectMeta.Annotations[longName] = normalLengthName 1325 _, err = client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1326 assert.Error(t, err) 1327 statusErr, ok = err.(*k8serrors.StatusError) 1328 assert.True(t, ok) 1329 assert.Len(t, statusErr.Status().Details.Causes, 1) 1330 assert.Equal(t, "spec.template.metadata.annotations", statusErr.Status().Details.Causes[0].Field) 1331 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1332 1333 goodFlt := defaultFleet(framework.Namespace) 1334 goodFlt.Spec.Template.ObjectMeta.Labels = make(map[string]string) 1335 goodFlt.Spec.Template.ObjectMeta.Labels["label"] = normalLengthName 1336 goodFlt, err = client.Fleets(framework.Namespace).Create(ctx, goodFlt, metav1.CreateOptions{}) 1337 require.NoError(t, err) 1338 defer client.Fleets(framework.Namespace).Delete(ctx, goodFlt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1339 err = framework.WaitForFleetCondition(t, goodFlt, e2e.FleetReadyCount(goodFlt.Spec.Replicas)) 1340 require.NoError(t, err) 1341 1342 // Verify validation on Update() 1343 flt, err = client.Fleets(framework.Namespace).Get(ctx, goodFlt.ObjectMeta.GetName(), metav1.GetOptions{}) 1344 require.NoError(t, err) 1345 goodFlt = flt.DeepCopy() 1346 goodFlt.Spec.Template.ObjectMeta.Annotations = make(map[string]string) 1347 goodFlt.Spec.Template.ObjectMeta.Annotations[longName] = normalLengthName 1348 _, err = client.Fleets(framework.Namespace).Update(ctx, goodFlt, metav1.UpdateOptions{}) 1349 assert.Error(t, err) 1350 statusErr, ok = err.(*k8serrors.StatusError) 1351 assert.True(t, ok) 1352 require.Len(t, statusErr.Status().Details.Causes, 1) 1353 assert.Equal(t, "spec.template.metadata.annotations", statusErr.Status().Details.Causes[0].Field) 1354 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1355 1356 // Make sure normal annotations path Validation on Update 1357 flt, err = client.Fleets(framework.Namespace).Get(ctx, goodFlt.ObjectMeta.GetName(), metav1.GetOptions{}) 1358 require.NoError(t, err) 1359 goodFlt = flt.DeepCopy() 1360 goodFlt.Spec.Template.ObjectMeta.Annotations = make(map[string]string) 1361 goodFlt.Spec.Template.ObjectMeta.Annotations[normalLengthName] = longName 1362 _, err = client.Fleets(framework.Namespace).Update(ctx, goodFlt, metav1.UpdateOptions{}) 1363 require.NoError(t, err) 1364 } 1365 1366 // TestFleetRecreateGameServers tests various gameserver shutdown scenarios to ensure 1367 // that recreation happens as expected 1368 func TestFleetRecreateGameServers(t *testing.T) { 1369 t.Parallel() 1370 ctx := context.Background() 1371 1372 tests := map[string]struct { 1373 f func(t *testing.T, list *agonesv1.GameServerList) 1374 }{ 1375 "pod deletion": {f: func(t *testing.T, list *agonesv1.GameServerList) { 1376 podClient := framework.KubeClient.CoreV1().Pods(framework.Namespace) 1377 1378 for _, gs := range list.Items { 1379 pod, err := podClient.Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1380 assert.NoError(t, err) 1381 1382 assert.True(t, metav1.IsControlledBy(pod, &gs)) 1383 1384 err = podClient.Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) 1385 assert.NoError(t, err) 1386 } 1387 }}, 1388 "gameserver shutdown": {f: func(t *testing.T, list *agonesv1.GameServerList) { 1389 for _, gs := range list.Items { 1390 var reply string 1391 reply, err := framework.SendGameServerUDP(t, &gs, "EXIT") 1392 if err != nil { 1393 // if we didn't get a response because the GameServer has gone away, then the packet dropped on the return, 1394 // but we're in the state we want, so we can ignore that we didn't get a response. 1395 _, gsErr := framework.AgonesClient.AgonesV1().GameServers(gs.ObjectMeta.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1396 if k8serrors.IsNotFound(gsErr) { 1397 continue 1398 } 1399 t.Fatalf("Could not message GameServer: %v", err) 1400 } 1401 1402 assert.Equal(t, "ACK: EXIT\n", reply) 1403 } 1404 }}, 1405 "gameserver unhealthy": {f: func(t *testing.T, list *agonesv1.GameServerList) { 1406 for _, gs := range list.Items { 1407 var reply string 1408 reply, err := framework.SendGameServerUDP(t, &gs, "UNHEALTHY") 1409 if err != nil { 1410 t.Fatalf("Could not message GameServer: %v", err) 1411 } 1412 1413 assert.Equal(t, "ACK: UNHEALTHY\n", reply) 1414 } 1415 }}, 1416 } 1417 1418 for k, v := range tests { 1419 t.Run(k, func(t *testing.T) { 1420 t.Parallel() 1421 client := framework.AgonesClient.AgonesV1() 1422 flt := defaultFleet(framework.Namespace) 1423 // add more game servers, to hunt for race conditions 1424 flt.Spec.Replicas = 10 1425 1426 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{}) 1427 require.NoError(t, err) 1428 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1429 1430 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1431 1432 list, err := listGameServers(ctx, flt, client) 1433 assert.NoError(t, err) 1434 var gameservers []agonesv1.GameServer 1435 for _, gs := range list.Items { 1436 if gs.Status.State != agonesv1.GameServerStateShutdown { 1437 gameservers = append(gameservers, gs) 1438 } 1439 } 1440 assert.Len(t, gameservers, int(flt.Spec.Replicas)) 1441 1442 // apply deletion function 1443 logrus.Info("applying deletion function") 1444 v.f(t, list) 1445 1446 for i, gs := range gameservers { 1447 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, true, func(ctx context.Context) (done bool, err error) { 1448 _, err = client.GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1449 1450 if err != nil && k8serrors.IsNotFound(err) { 1451 logrus.Infof("gameserver %d/%d not found", i+1, flt.Spec.Replicas) 1452 return true, nil 1453 } 1454 1455 return false, err 1456 }) 1457 assert.NoError(t, err) 1458 } 1459 1460 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1461 }) 1462 } 1463 } 1464 1465 // TestFleetResourceValidation - check that we are not able to use 1466 // invalid PodTemplate for GameServer Spec with wrong Resource Requests and Limits 1467 func TestFleetResourceValidation(t *testing.T) { 1468 t.Parallel() 1469 ctx := context.Background() 1470 1471 client := framework.AgonesClient.AgonesV1() 1472 1473 // check two Containers in Gameserver Spec Template validation 1474 flt := defaultFleet(framework.Namespace) 1475 containerName := "container2" 1476 resources := corev1.ResourceRequirements{ 1477 Requests: corev1.ResourceList{ 1478 corev1.ResourceCPU: resource.MustParse("30m"), 1479 corev1.ResourceMemory: resource.MustParse("32Mi"), 1480 }, 1481 Limits: corev1.ResourceList{ 1482 corev1.ResourceCPU: resource.MustParse("30m"), 1483 corev1.ResourceMemory: resource.MustParse("32Mi"), 1484 }, 1485 } 1486 flt.Spec.Template.Spec.Template = 1487 corev1.PodTemplateSpec{ 1488 Spec: corev1.PodSpec{ 1489 Containers: []corev1.Container{ 1490 {Name: "container", Image: framework.GameServerImage, Resources: *(resources.DeepCopy())}, 1491 {Name: containerName, Image: framework.GameServerImage, Resources: *(resources.DeepCopy())}, 1492 }, 1493 }, 1494 } 1495 mi128 := resource.MustParse("128Mi") 1496 m50 := resource.MustParse("50m") 1497 1498 flt.Spec.Template.Spec.Container = containerName 1499 containers := flt.Spec.Template.Spec.Template.Spec.Containers 1500 containers[1].Resources.Limits[corev1.ResourceMemory] = resource.MustParse("64Mi") 1501 containers[1].Resources.Requests[corev1.ResourceMemory] = mi128 1502 1503 _, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1504 assert.NotNil(t, err) 1505 statusErr, ok := err.(*k8serrors.StatusError) 1506 assert.True(t, ok) 1507 assert.Len(t, statusErr.Status().Details.Causes, 1) 1508 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1509 assert.Equal(t, "spec.template.spec.template.spec.containers[1].resources.requests", statusErr.Status().Details.Causes[0].Field) 1510 1511 containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("-50m") 1512 _, err = client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1513 assert.NotNil(t, err) 1514 statusErr, ok = err.(*k8serrors.StatusError) 1515 assert.True(t, ok) 1516 1517 assert.Len(t, statusErr.Status().Details.Causes, 3) 1518 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1519 assert.Equal(t, "spec.template.spec.template.spec.containers[0].resources.limits[cpu]", statusErr.Status().Details.Causes[0].Field) 1520 causes := statusErr.Status().Details.Causes 1521 assertCausesContainsString(t, causes, `Invalid value: "30m": must be less than or equal to cpu limit of -50m`) 1522 assertCausesContainsString(t, causes, `Invalid value: "-50m": must be greater than or equal to 0`) 1523 assertCausesContainsString(t, causes, `Invalid value: "128Mi": must be less than or equal to memory limit of 64Mi`) 1524 1525 containers[1].Resources.Limits[corev1.ResourceMemory] = mi128 1526 containers[0].Resources.Limits[corev1.ResourceCPU] = m50 1527 flt, err = client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1528 if assert.NoError(t, err) { 1529 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1530 } 1531 1532 containers = flt.Spec.Template.Spec.Template.Spec.Containers 1533 assert.Equal(t, mi128, containers[1].Resources.Limits[corev1.ResourceMemory]) 1534 assert.Equal(t, m50, containers[0].Resources.Limits[corev1.ResourceCPU]) 1535 } 1536 1537 func TestFleetAggregatedPlayerStatus(t *testing.T) { 1538 if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) { 1539 t.SkipNow() 1540 } 1541 t.Parallel() 1542 ctx := context.Background() 1543 client := framework.AgonesClient.AgonesV1() 1544 1545 flt := defaultFleet(framework.Namespace) 1546 flt.Spec.Template.Spec.Players = &agonesv1.PlayersSpec{ 1547 InitialCapacity: 10, 1548 } 1549 1550 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1551 assert.NoError(t, err) 1552 1553 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 1554 if fleet.Status.Players == nil { 1555 log.WithField("status", fleet.Status).Info("No Players") 1556 return false 1557 } 1558 1559 log.WithField("status", fleet.Status).Info("Checking Capacity") 1560 return fleet.Status.Players.Capacity == 30 1561 }) 1562 1563 list, err := framework.ListGameServersFromFleet(flt) 1564 assert.NoError(t, err) 1565 // set 3 random capacities, and connect a random number of players 1566 totalCapacity := 0 1567 totalPlayers := 0 1568 for i := range list { 1569 // Do this, otherwise scopelint complains about "using a reference for the variable on range scope" 1570 gs := &list[i] 1571 players := rand.IntnRange(1, 5) 1572 capacity := rand.IntnRange(players, 100) 1573 totalCapacity += capacity 1574 1575 msg := fmt.Sprintf("PLAYER_CAPACITY %d", capacity) 1576 reply, err := framework.SendGameServerUDP(t, gs, msg) 1577 if err != nil { 1578 t.Fatalf("Could not message GameServer: %v", err) 1579 } 1580 assert.Equal(t, fmt.Sprintf("ACK: %s\n", msg), reply) 1581 1582 totalPlayers += players 1583 for i := 1; i <= players; i++ { 1584 msg := "PLAYER_CONNECT " + fmt.Sprintf("%d", i) 1585 logrus.WithField("msg", msg).WithField("gs", gs.ObjectMeta.Name).Info("Sending Player Connect") 1586 // retry on failure. Will stop flakiness of UDP packets being sent/received. 1587 err := wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, true, func(_ context.Context) (done bool, err error) { 1588 reply, err := framework.SendGameServerUDP(t, gs, msg) 1589 if err != nil { 1590 logrus.WithError(err).Warn("error with udp packet") 1591 return false, nil 1592 } 1593 assert.Equal(t, fmt.Sprintf("ACK: %s\n", msg), reply) 1594 return true, nil 1595 }) 1596 assert.NoError(t, err) 1597 } 1598 } 1599 1600 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 1601 log.WithField("players", fleet.Status.Players).WithField("totalCapacity", totalCapacity). 1602 WithField("totalPlayers", totalPlayers).Info("Checking Capacity") 1603 // since UDP packets might fail, we might get an extra player, so we'll check for that. 1604 return (fleet.Status.Players.Capacity == int64(totalCapacity)) && (fleet.Status.Players.Count >= int64(totalPlayers)) 1605 }) 1606 } 1607 1608 func TestFleetAggregatedCounterStatus(t *testing.T) { 1609 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 1610 t.SkipNow() 1611 } 1612 t.Parallel() 1613 ctx := context.Background() 1614 client := framework.AgonesClient.AgonesV1() 1615 1616 flt := defaultFleet(framework.Namespace) 1617 flt.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{ 1618 "games": { 1619 Count: 1, 1620 Capacity: 10, 1621 }, 1622 } 1623 1624 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1625 require.NoError(t, err) 1626 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1627 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1628 1629 // allocate two of them. 1630 framework.CreateAndApplyAllocation(t, flt) 1631 framework.CreateAndApplyAllocation(t, flt) 1632 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 1633 return fleet.Status.AllocatedReplicas == 2 1634 }) 1635 1636 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 1637 counter, ok := fleet.Status.Counters["games"] 1638 if !ok { 1639 log.WithField("status", fleet.Status).Info("No games Counter") 1640 return false 1641 } 1642 1643 log.WithField("status", fleet.Status).Info("Checking Count and Capacity") 1644 log.WithField("AggregatedCounterStatus", counter).Debug("AggregatedCounterStatus") 1645 return counter.AllocatedCount == 2 && counter.AllocatedCapacity == 20 && counter.Count == 3 && counter.Capacity == 30 1646 }) 1647 1648 list, err := framework.ListGameServersFromFleet(flt) 1649 assert.NoError(t, err) 1650 totalCapacity := 0 1651 totalCount := 0 1652 allocatedCapacity := 0 1653 allocatedCount := 0 1654 // set random counts and capacities for each gameserver 1655 for _, gs := range list { 1656 count := rand.IntnRange(2, 9) 1657 capacity := rand.IntnRange(count, 100) 1658 1659 totalCapacity += capacity 1660 msg := fmt.Sprintf("SET_COUNTER_CAPACITY games %d", capacity) 1661 reply, err := framework.SendGameServerUDP(t, &gs, msg) 1662 require.NoError(t, err) 1663 assert.Equal(t, "SUCCESS\n", reply) 1664 1665 totalCount += count 1666 msg = fmt.Sprintf("SET_COUNTER_COUNT games %d", count) 1667 reply, err = framework.SendGameServerUDP(t, &gs, msg) 1668 require.NoError(t, err) 1669 assert.Equal(t, "SUCCESS\n", reply) 1670 1671 if gs.Status.State == agonesv1.GameServerStateAllocated { 1672 allocatedCapacity += capacity 1673 allocatedCount += count 1674 } 1675 } 1676 1677 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 1678 counter, ok := fleet.Status.Counters["games"] 1679 if !ok { 1680 log.WithField("status", fleet.Status).Info("No games Counter") 1681 return false 1682 } 1683 1684 log.WithField("status", fleet.Status).Info("Checking Aggregated Count and Capacity") 1685 log.WithField("AggregatedCounterStatus", counter).Debug("AggregatedCounterStatus") 1686 return counter.AllocatedCount == int64(allocatedCount) && counter.AllocatedCapacity == int64(allocatedCapacity) && 1687 counter.Count == int64(totalCount) && counter.Capacity == int64(totalCapacity) 1688 }) 1689 } 1690 1691 func TestFleetAggregatedListStatus(t *testing.T) { 1692 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 1693 t.SkipNow() 1694 } 1695 t.Parallel() 1696 ctx := context.Background() 1697 client := framework.AgonesClient.AgonesV1() 1698 1699 flt := defaultFleet(framework.Namespace) 1700 flt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{ 1701 "gamers": { 1702 Values: []string{"gamer0", "gamer1"}, 1703 Capacity: 10, 1704 }, 1705 } 1706 1707 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1708 require.NoError(t, err) 1709 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1710 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1711 1712 // allocate two of them. 1713 framework.CreateAndApplyAllocation(t, flt) 1714 framework.CreateAndApplyAllocation(t, flt) 1715 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 1716 return fleet.Status.AllocatedReplicas == 2 1717 }) 1718 1719 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 1720 list, ok := fleet.Status.Lists["gamers"] 1721 if !ok { 1722 log.WithField("status", fleet.Status).Info("No gamers List") 1723 return false 1724 } 1725 1726 log.WithField("status", fleet.Status).Info("Checking Count and Capacity") 1727 log.WithField("AggregatedListStatus", list).Debug("AggregatedListStatus") 1728 return list.AllocatedCount == 4 && list.AllocatedCapacity == 20 && list.Count == 6 && list.Capacity == 30 1729 }) 1730 1731 list, err := framework.ListGameServersFromFleet(flt) 1732 assert.NoError(t, err) 1733 totalCapacity := 0 1734 totalCount := 0 1735 allocatedCapacity := 0 1736 allocatedCount := 0 1737 // set random counts and capacities for each gameserver 1738 for _, gs := range list { 1739 count := rand.IntnRange(2, 9) 1740 capacity := rand.IntnRange(count, 100) 1741 1742 totalCapacity += capacity 1743 msg := fmt.Sprintf("SET_LIST_CAPACITY gamers %d", capacity) 1744 reply, err := framework.SendGameServerUDP(t, &gs, msg) 1745 require.NoError(t, err) 1746 assert.Equal(t, "SUCCESS\n", reply) 1747 1748 totalCount += count 1749 // Each list starts with a count of 2 (Values: []string{"gamer0", "gamer1"}) 1750 for i := 2; i < count; i++ { 1751 msg = fmt.Sprintf("APPEND_LIST_VALUE gamers gamer%d", i) 1752 reply, err = framework.SendGameServerUDP(t, &gs, msg) 1753 require.NoError(t, err) 1754 assert.Equal(t, "SUCCESS\n", reply) 1755 } 1756 1757 if gs.Status.State == agonesv1.GameServerStateAllocated { 1758 allocatedCapacity += capacity 1759 allocatedCount += count 1760 } 1761 } 1762 1763 framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool { 1764 list, ok := fleet.Status.Lists["gamers"] 1765 if !ok { 1766 log.WithField("status", fleet.Status).Info("No gamers List") 1767 return false 1768 } 1769 1770 log.WithField("status", fleet.Status).Info("Checking Aggregated Count and Capacity") 1771 log.WithField("AggregatedListStatus", list).Debug("AggregatedListStatus") 1772 return list.AllocatedCount == int64(allocatedCount) && list.AllocatedCapacity == int64(allocatedCapacity) && 1773 list.Count == int64(totalCount) && list.Capacity == int64(totalCapacity) 1774 }) 1775 } 1776 1777 func TestFleetAllocationOverflow(t *testing.T) { 1778 t.Parallel() 1779 ctx := context.Background() 1780 client := framework.AgonesClient.AgonesV1() 1781 fleets := client.Fleets(framework.Namespace) 1782 1783 setup := func() *agonesv1.Fleet { 1784 flt := defaultFleet(framework.Namespace) 1785 flt.Spec.AllocationOverflow = &agonesv1.AllocationOverflow{Labels: map[string]string{"colour": "green"}, Annotations: map[string]string{"action": "update"}} 1786 flt, err := fleets.Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 1787 require.NoError(t, err) 1788 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1789 1790 // allocate two of them. 1791 framework.CreateAndApplyAllocation(t, flt) 1792 framework.CreateAndApplyAllocation(t, flt) 1793 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 1794 return fleet.Status.AllocatedReplicas == 2 1795 }) 1796 1797 flt, err = fleets.Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{}) 1798 require.NoError(t, err) 1799 return flt 1800 } 1801 1802 assertCount := func(t *testing.T, log *logrus.Entry, flt *agonesv1.Fleet, expected int) { 1803 require.Eventuallyf(t, func() bool { 1804 log.Info("Checking GameServers") 1805 list, err := framework.ListGameServersFromFleet(flt) 1806 require.NoError(t, err) 1807 count := 0 1808 1809 for _, gs := range list { 1810 if gs.ObjectMeta.Labels["colour"] != "green" { 1811 log.WithField("gs", gs).Info("Label not set") 1812 continue 1813 } 1814 if gs.ObjectMeta.Annotations["action"] != "update" { 1815 log.WithField("gs", gs).Info("Annotation not set") 1816 continue 1817 } 1818 count++ 1819 } 1820 1821 return count == expected 1822 }, 5*time.Minute, time.Second, "Labels and annotations not set") 1823 } 1824 1825 t.Run("scale down", func(t *testing.T) { 1826 log := e2e.TestLogger(t) 1827 flt := setup() 1828 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 1829 1830 framework.ScaleFleet(t, log, flt, 0) 1831 1832 // wait for scale down 1833 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 1834 return fleet.Status.AllocatedReplicas == 2 && fleet.Status.ReadyReplicas == 0 1835 }) 1836 1837 assertCount(t, log, flt, 2) 1838 }) 1839 1840 t.Run("rolling update", func(t *testing.T) { 1841 log := e2e.TestLogger(t) 1842 flt := setup() 1843 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 1844 1845 fltCopy := flt.DeepCopy() 1846 if fltCopy.Spec.Template.ObjectMeta.Labels == nil { 1847 fltCopy.Spec.Template.ObjectMeta.Labels = map[string]string{} 1848 } 1849 fltCopy.Spec.Template.ObjectMeta.Labels["version"] = "2.0" 1850 flt, err := fleets.Update(ctx, fltCopy, metav1.UpdateOptions{}) 1851 require.NoError(t, err) 1852 1853 // wait for rolling update to finish 1854 require.Eventuallyf(t, func() bool { 1855 list, err := framework.ListGameServersFromFleet(flt) 1856 assert.NoError(t, err) 1857 for _, gs := range list { 1858 log.WithField("gs", gs).Info("checking game server") 1859 if gs.Status.State == agonesv1.GameServerStateReady && gs.ObjectMeta.Labels["version"] == "2.0" { 1860 return true 1861 } 1862 } 1863 1864 return false 1865 }, 5*time.Minute, time.Second, "Rolling update did not complete") 1866 1867 if runtime.FeatureEnabled(runtime.FeatureRollingUpdateFix) { 1868 // In the rolling update fix, the old GSS will be scaled to Spec.Replicas=0. 1869 assertCount(t, log, flt, 2) 1870 } else { 1871 assertCount(t, log, flt, 1) 1872 } 1873 }) 1874 } 1875 1876 func assertCausesContainsString(t *testing.T, causes []metav1.StatusCause, expected string) { 1877 strs := make([]string, 0, len(causes)) 1878 for _, v := range causes { 1879 strs = append(strs, v.Message) 1880 } 1881 assert.Contains(t, strs, expected) 1882 } 1883 1884 func listGameServers(ctx context.Context, flt *agonesv1.Fleet, getter typedagonesv1.GameServersGetter) (*agonesv1.GameServerList, error) { 1885 selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}) 1886 return getter.GameServers(framework.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) 1887 } 1888 1889 // Counts the number of gameservers with the specified scheduling strategy in a fleet 1890 func countFleetScheduling(gsList []agonesv1.GameServer, scheduling apis.SchedulingStrategy) int { 1891 count := 0 1892 for i := range gsList { 1893 gs := &gsList[i] 1894 if gs.Spec.Scheduling == scheduling { 1895 count++ 1896 } 1897 } 1898 return count 1899 } 1900 1901 // Patches fleet with scheduling and scale values 1902 func schedulingFleetPatch(ctx context.Context, t *testing.T, f *agonesv1.Fleet, scheduling apis.SchedulingStrategy, scale int32) *agonesv1.Fleet { 1903 1904 patch := fmt.Sprintf(`[{ "op": "replace", "path": "/spec/scheduling", "value": "%s" }, 1905 { "op": "replace", "path": "/spec/replicas", "value": %d }]`, 1906 scheduling, scale) 1907 1908 logrus.WithField("fleet", f.ObjectMeta.Name). 1909 WithField("scheduling", scheduling). 1910 WithField("scale", scale). 1911 WithField("patch", patch). 1912 Info("updating scheduling") 1913 1914 fltRes, err := framework.AgonesClient. 1915 AgonesV1(). 1916 Fleets(framework.Namespace). 1917 Patch(ctx, f.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{}) 1918 1919 require.NoError(t, err) 1920 return fltRes 1921 } 1922 1923 func scaleAndWait(ctx context.Context, t *testing.T, flt *agonesv1.Fleet, fleetSize int32) (duration time.Duration, err error) { 1924 t0 := time.Now() 1925 scaleFleetSubresource(ctx, t, flt, fleetSize) 1926 err = framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(fleetSize)) 1927 duration = time.Since(t0) 1928 return 1929 } 1930 1931 // scaleFleetPatch creates a patch to apply to a Fleet. 1932 // Easier for testing, as it removes object generational issues. 1933 func scaleFleetPatch(ctx context.Context, t *testing.T, f *agonesv1.Fleet, scale int32) *agonesv1.Fleet { 1934 patch := fmt.Sprintf(`[{ "op": "replace", "path": "/spec/replicas", "value": %d }]`, scale) 1935 logrus.WithField("fleet", f.ObjectMeta.Name).WithField("scale", scale).WithField("patch", patch).Info("Scaling fleet") 1936 1937 fltRes, err := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Patch(ctx, f.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{}) 1938 require.NoError(t, err) 1939 return fltRes 1940 } 1941 1942 // scaleFleetSubresource uses scale subresource to change Replicas size of the Fleet. 1943 // Returns the same f as in parameter, just to keep signature in sync with scaleFleetPatch 1944 func scaleFleetSubresource(ctx context.Context, t *testing.T, f *agonesv1.Fleet, scale int32) *agonesv1.Fleet { 1945 logrus.WithField("fleet", f.ObjectMeta.Name).WithField("scale", scale).Info("Scaling fleet") 1946 1947 err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 1948 client := framework.AgonesClient.AgonesV1() 1949 // GetScale returns current Scale object with resourceVersion which is opaque object 1950 // and it will be used to create new Scale object 1951 opts := metav1.GetOptions{} 1952 sc, err := client.Fleets(framework.Namespace).GetScale(ctx, f.ObjectMeta.Name, opts) 1953 if err != nil { 1954 return err 1955 } 1956 1957 sc2 := newScale(f.Name, scale, sc.ObjectMeta.ResourceVersion) 1958 _, err = client.Fleets(framework.Namespace).UpdateScale(ctx, f.ObjectMeta.Name, sc2, metav1.UpdateOptions{}) 1959 return err 1960 }) 1961 1962 if err != nil { 1963 t.Fatal("could not update the scale subresource") 1964 } 1965 return f 1966 } 1967 1968 // defaultFleet returns a default fleet configuration 1969 func defaultFleet(namespace string) *agonesv1.Fleet { 1970 gs := framework.DefaultGameServer(namespace) 1971 return fleetWithGameServerSpec(&gs.Spec, namespace) 1972 } 1973 1974 // defaultEmptyFleet returns a default fleet configuration with no replicas. 1975 func defaultEmptyFleet(namespace string) *agonesv1.Fleet { 1976 gs := framework.DefaultGameServer(namespace) 1977 return fleetWithGameServerSpecAndReplicas(&gs.Spec, namespace, 0) 1978 } 1979 1980 // fleetWithGameServerSpec returns a fleet with specified gameserver spec 1981 func fleetWithGameServerSpec(gsSpec *agonesv1.GameServerSpec, namespace string) *agonesv1.Fleet { 1982 return fleetWithGameServerSpecAndReplicas(gsSpec, namespace, replicasCount) 1983 } 1984 1985 // fleetWithGameServerSpecAndReplicas returns a fleet with specified gameserver spec and specified replica count 1986 func fleetWithGameServerSpecAndReplicas(gsSpec *agonesv1.GameServerSpec, namespace string, replicas int32) *agonesv1.Fleet { 1987 return &agonesv1.Fleet{ 1988 ObjectMeta: metav1.ObjectMeta{GenerateName: "simple-fleet-1.0", Namespace: namespace}, 1989 Spec: agonesv1.FleetSpec{ 1990 Replicas: replicas, 1991 Template: agonesv1.GameServerTemplateSpec{ 1992 Spec: *gsSpec, 1993 }, 1994 }, 1995 } 1996 } 1997 1998 // newScale returns a scale with specified Replicas spec 1999 func newScale(fleetName string, newReplicas int32, resourceVersion string) *autoscalingv1.Scale { 2000 return &autoscalingv1.Scale{ 2001 ObjectMeta: metav1.ObjectMeta{Name: fleetName, Namespace: framework.Namespace, ResourceVersion: resourceVersion}, 2002 Spec: autoscalingv1.ScaleSpec{ 2003 Replicas: newReplicas, 2004 }, 2005 } 2006 }