From be750b935224a8d71c6f333da9e8019ea62124a3 Mon Sep 17 00:00:00 2001 From: Ivy Gooch Date: Tue, 21 Nov 2023 21:05:59 +0000 Subject: [PATCH] Adds fleetauotscaler e2e test for Lists --- pkg/fleetautoscalers/fleetautoscalers_test.go | 29 ++ test/e2e/fleetautoscaler_test.go | 272 +++++++++++++++++- 2 files changed, 299 insertions(+), 2 deletions(-) diff --git a/pkg/fleetautoscalers/fleetautoscalers_test.go b/pkg/fleetautoscalers/fleetautoscalers_test.go index 9cf7e24c57..b6c89479ab 100644 --- a/pkg/fleetautoscalers/fleetautoscalers_test.go +++ b/pkg/fleetautoscalers/fleetautoscalers_test.go @@ -1455,6 +1455,7 @@ func TestApplyCounterPolicy(t *testing.T) { } // nolint:dupl // Linter errors on lines are duplicate of TestApplyCounterPolicy +// NOTE: Does not test for the validity of a fleet autoscaler policy (ValidateListPolicy) func TestApplyListPolicy(t *testing.T) { t.Parallel() @@ -1594,6 +1595,34 @@ func TestApplyListPolicy(t *testing.T) { wantErr: false, }, }, + "scale up to maxcapacity": { + fleet: modifiedFleet(func(f *agonesv1.Fleet) { + f.Spec.Template.Spec.Lists = make(map[string]agonesv1.ListStatus) + f.Spec.Template.Spec.Lists["gamers"] = agonesv1.ListStatus{ + Values: []string{"default", "default2", "default3"}, + Capacity: 5} + f.Status.Replicas = 3 + f.Status.ReadyReplicas = 3 + f.Status.AllocatedReplicas = 0 + f.Status.Lists = make(map[string]agonesv1.AggregatedListStatus) + f.Status.Lists["gamers"] = agonesv1.AggregatedListStatus{ + Count: 9, + Capacity: 15, + } + }), + featureFlags: string(utilruntime.FeatureCountsAndLists) + "=true", + lp: &autoscalingv1.ListPolicy{ + Key: "gamers", + MaxCapacity: 25, + MinCapacity: 15, + BufferSize: intstr.FromInt(15), + }, + want: expected{ + replicas: 5, + limited: true, + wantErr: false, + }, + }, "scale down": { fleet: modifiedFleet(func(f *agonesv1.Fleet) { f.Spec.Template.Spec.Lists = make(map[string]agonesv1.ListStatus) diff --git a/test/e2e/fleetautoscaler_test.go b/test/e2e/fleetautoscaler_test.go index eee64c392d..43f7ac31a6 100644 --- a/test/e2e/fleetautoscaler_test.go +++ b/test/e2e/fleetautoscaler_test.go @@ -816,7 +816,7 @@ func TestCounterAutoscaler(t *testing.T) { counterFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler { fas := autoscalingv1.FleetAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-autoscaler", Namespace: framework.Namespace}, + ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-counter-autoscaler", Namespace: framework.Namespace}, Spec: autoscalingv1.FleetAutoscalerSpec{ FleetName: flt.ObjectMeta.Name, Policy: autoscalingv1.FleetAutoscalerPolicy{ @@ -989,6 +989,7 @@ func TestCounterAutoscalerAllocated(t *testing.T) { wantReadyGs: 2, }, } + // nolint:dupl // Linter errors on lines are duplicate of TestListAutoscalerAllocated for name, testCase := range testCases { t.Run(name, func(t *testing.T) { flt, err := client.Fleets(framework.Namespace).Create(ctx, defaultFlt.DeepCopy(), metav1.CreateOptions{}) @@ -1014,7 +1015,7 @@ func TestCounterAutoscalerAllocated(t *testing.T) { }) counterFas := &autoscalingv1.FleetAutoscaler{ - ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-autoscaler", Namespace: framework.Namespace}, + ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-counter-autoscaler", Namespace: framework.Namespace}, Spec: autoscalingv1.FleetAutoscalerSpec{ FleetName: flt.ObjectMeta.Name, Policy: autoscalingv1.FleetAutoscalerPolicy{ @@ -1040,3 +1041,270 @@ func TestCounterAutoscalerAllocated(t *testing.T) { }) } } + +func TestListAutoscaler(t *testing.T) { + if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { + t.SkipNow() + } + t.Parallel() + + ctx := context.Background() + client := framework.AgonesClient.AgonesV1() + log := e2e.TestLogger(t) + + flt := defaultFleet(framework.Namespace) + flt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{ + "games": { + Values: []string{"game1", "game2", "game3"}, // AggregateCount 9 + Capacity: 5, // AggregateCapacity 15 + }, + } + + flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) + require.NoError(t, err) + defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck + framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + + fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace) + + listFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler { + fas := autoscalingv1.FleetAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-list-autoscaler", Namespace: framework.Namespace}, + Spec: autoscalingv1.FleetAutoscalerSpec{ + FleetName: flt.ObjectMeta.Name, + Policy: autoscalingv1.FleetAutoscalerPolicy{ + Type: autoscalingv1.ListPolicyType, + }, + Sync: &autoscalingv1.FleetAutoscalerSync{ + Type: autoscalingv1.FixedIntervalSyncType, + FixedInterval: autoscalingv1.FixedIntervalSync{ + Seconds: 1, + }, + }, + }, + } + f(&fas.Spec.Policy) + return &fas + } + testCases := map[string]struct { + fas *autoscalingv1.FleetAutoscaler + wantFasErr bool + wantReplicas int32 + }{ + "Scale Down to Minimum 1 Replica": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(2), + MinCapacity: 0, + MaxCapacity: 3, + } + }), + wantFasErr: false, + wantReplicas: 1, // Count:3 Capacity:5 + }, + "Scale Down to Buffer": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(3), + MinCapacity: 0, + MaxCapacity: 5, + } + }), + wantFasErr: false, + wantReplicas: 2, // Count:6 Capacity:10 + }, + "MinCapacity Must Be Greater Than Zero Percentage Buffer": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromString("50%"), + MinCapacity: 0, + MaxCapacity: 100, + } + }), + wantFasErr: true, + wantReplicas: 3, + }, + "Scale Up to MinCapacity": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(3), + MinCapacity: 16, + MaxCapacity: 100, + } + }), + wantFasErr: false, + wantReplicas: 4, // Count:12 Capacity:20 + }, + "Scale Down to MinCapacity": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(1), + MinCapacity: 10, + MaxCapacity: 100, + } + }), + wantFasErr: false, + wantReplicas: 2, // Count:6 Capacity:10 + }, + "MinCapacity Less Than Buffer Invalid": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(15), + MinCapacity: 5, + MaxCapacity: 25, + } + }), + wantFasErr: true, + wantReplicas: 3, + }, + "Scale Up to Buffer": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(15), + MinCapacity: 15, + MaxCapacity: 100, + } + }), + wantFasErr: false, + wantReplicas: 8, // Count:24 Capacity:40 + }, + "Scale Up to MaxCapacity": { + fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.List = &autoscalingv1.ListPolicy{ + Key: "games", + BufferSize: intstr.FromInt(15), + MinCapacity: 15, + MaxCapacity: 25, + } + }), + wantFasErr: false, + wantReplicas: 5, // Count:15 Capacity:25 + }, + } + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + + fas, err := fleetautoscalers.Create(ctx, testCase.fas, metav1.CreateOptions{}) + if testCase.wantFasErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + log.Print("FAS: ", fas) + + framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas)) + fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck + + // Return to starting 3 replicas + framework.ScaleFleet(t, log, flt, 3) + framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(3)) + }) + } +} + +func TestListAutoscalerAllocated(t *testing.T) { + if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { + t.SkipNow() + } + t.Parallel() + + ctx := context.Background() + client := framework.AgonesClient.AgonesV1() + log := e2e.TestLogger(t) + + defaultFlt := defaultFleet(framework.Namespace) + defaultFlt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{ + "gamers": { + Values: []string{"gamer1", "gamer2", "gamer3", "gamer4"}, // AggregateCount 12 + Capacity: 6, // AggregateCapacity 18 + }, + } + + fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace) + + testCases := map[string]struct { + fas autoscalingv1.ListPolicy + wantAllocatedGs int32 // Must be >= 0 && <= 3 + wantReadyGs int32 + }{ + "Scale Down Buffer Percent": { + fas: autoscalingv1.ListPolicy{ + Key: "gamers", + BufferSize: intstr.FromString("50%"), + MinCapacity: 6, + MaxCapacity: 60, + }, + wantAllocatedGs: 0, + wantReadyGs: 1, + }, + "Scale Up Buffer Percent": { + fas: autoscalingv1.ListPolicy{ + Key: "gamers", + BufferSize: intstr.FromString("50%"), + MinCapacity: 6, + MaxCapacity: 60, + }, + wantAllocatedGs: 3, + wantReadyGs: 3, + }, + } + // nolint:dupl // Linter errors on lines are duplicate of TestCounterAutoscalerAllocated + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + flt, err := client.Fleets(framework.Namespace).Create(ctx, defaultFlt.DeepCopy(), metav1.CreateOptions{}) + require.NoError(t, err) + defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck + framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + + gsa := allocationv1.GameServerAllocation{ + Spec: allocationv1.GameServerAllocationSpec{ + Selectors: []allocationv1.GameServerSelector{ + {LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}, + }}} + + // Allocate game servers, as Buffer Percent scales up (or down) based on allocated aggregate capacity + for i := int32(0); i < testCase.wantAllocatedGs; i++ { + _, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) + require.NoError(t, err) + } + framework.AssertFleetCondition(t, flt, func(entry *logrus.Entry, fleet *agonesv1.Fleet) bool { + log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking for game server allocations") + return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGs + }) + + listFas := &autoscalingv1.FleetAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-list-autoscaler", Namespace: framework.Namespace}, + Spec: autoscalingv1.FleetAutoscalerSpec{ + FleetName: flt.ObjectMeta.Name, + Policy: autoscalingv1.FleetAutoscalerPolicy{ + Type: autoscalingv1.ListPolicyType, + List: &testCase.fas, + }, + Sync: &autoscalingv1.FleetAutoscalerSync{ + Type: autoscalingv1.FixedIntervalSyncType, + FixedInterval: autoscalingv1.FixedIntervalSync{ + Seconds: 1, + }, + }, + }, + } + + fas, err := fleetautoscalers.Create(ctx, listFas, metav1.CreateOptions{}) + assert.NoError(t, err) + defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck + + framework.AssertFleetCondition(t, flt, func(entry *logrus.Entry, fleet *agonesv1.Fleet) bool { + return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGs && fleet.Status.ReadyReplicas == testCase.wantReadyGs + }) + }) + } +}