diff --git a/pkg/fleetautoscalers/fleetautoscalers_test.go b/pkg/fleetautoscalers/fleetautoscalers_test.go index eec82195c0..5a67717363 100644 --- a/pkg/fleetautoscalers/fleetautoscalers_test.go +++ b/pkg/fleetautoscalers/fleetautoscalers_test.go @@ -772,6 +772,31 @@ func TestApplyCounterPolicy(t *testing.T) { wantErr: true, }, }, + "Counter based fleet does not have any replicas": { + fleet: modifiedFleet(func(f *agonesv1.Fleet) { + f.Spec.Template.Spec.Counters = make(map[string]agonesv1.CounterStatus) + f.Spec.Template.Spec.Counters["gamers"] = agonesv1.CounterStatus{ + Count: 0, + Capacity: 7} + f.Status.Replicas = 0 + f.Status.ReadyReplicas = 0 + f.Status.AllocatedReplicas = 0 + f.Status.Counters = make(map[string]agonesv1.AggregatedCounterStatus) + f.Status.Counters["gamers"] = agonesv1.AggregatedCounterStatus{} + }), + featureFlags: string(utilruntime.FeatureCountsAndLists) + "=true", + cp: &autoscalingv1.CounterPolicy{ + Key: "gamers", + MaxCapacity: 100, + MinCapacity: 10, + BufferSize: intstr.FromInt(10), + }, + want: expected{ + replicas: 2, + limited: true, + wantErr: false, + }, + }, "fleet spec does not have counter": { fleet: modifiedFleet(func(f *agonesv1.Fleet) { f.Spec.Template.Spec.Counters = make(map[string]agonesv1.CounterStatus) @@ -1570,7 +1595,7 @@ func TestApplyListPolicy(t *testing.T) { wantErr: true, }, }, - "fleet does not have any replicas": { + "List based fleet does not have any replicas": { fleet: modifiedFleet(func(f *agonesv1.Fleet) { f.Spec.Template.Spec.Lists = make(map[string]agonesv1.ListStatus) f.Spec.Template.Spec.Lists["gamers"] = agonesv1.ListStatus{ diff --git a/pkg/gameserversets/controller.go b/pkg/gameserversets/controller.go index dc0b169a7b..0feef01729 100644 --- a/pkg/gameserversets/controller.go +++ b/pkg/gameserversets/controller.go @@ -639,6 +639,7 @@ func computeStatus(gsSet *agonesv1.GameServerSet, list []*agonesv1.GameServer) a // Initialize list status with empty lists from spec if runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { status.Lists = createInitialListStatus(gsSet) + status.Counters = createInitialCounterStatus(gsSet) } for _, gs := range list { if gs.IsBeingDeleted() { @@ -700,6 +701,14 @@ func createInitialListStatus(gsSet *agonesv1.GameServerSet) map[string]agonesv1. return list } +func createInitialCounterStatus(gsSet *agonesv1.GameServerSet) map[string]agonesv1.AggregatedCounterStatus { + counters := make(map[string]agonesv1.AggregatedCounterStatus) + for name := range gsSet.Spec.Template.Spec.Counters { + counters[name] = agonesv1.AggregatedCounterStatus{} + } + return counters +} + // aggregateCounters adds the contents of a CounterStatus map to an AggregatedCounterStatus map. func aggregateCounters(aggCounterStatus map[string]agonesv1.AggregatedCounterStatus, counterStatus map[string]agonesv1.CounterStatus, diff --git a/pkg/gameserversets/controller_test.go b/pkg/gameserversets/controller_test.go index 511be442d9..d7fe7a2395 100644 --- a/pkg/gameserversets/controller_test.go +++ b/pkg/gameserversets/controller_test.go @@ -412,6 +412,44 @@ func TestComputeStatus(t *testing.T) { assert.Equal(t, expected, computeStatus(gsSet, list)) }) + t.Run("counters with no gameservers", func(t *testing.T) { + utilruntime.FeatureTestMutex.Lock() + defer utilruntime.FeatureTestMutex.Unlock() + + require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists))) + + gsSet := defaultFixture() + gsSet.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{ + "firstCounter": {Capacity: 10, Count: 1}, + "secondCounter": {Capacity: 10, Count: 1}, + } + var list []*agonesv1.GameServer + + expected := agonesv1.GameServerSetStatus{ + Replicas: 0, + ReadyReplicas: 0, + ReservedReplicas: 0, + AllocatedReplicas: 0, + Lists: map[string]agonesv1.AggregatedListStatus{}, + Counters: map[string]agonesv1.AggregatedCounterStatus{ + "firstCounter": { + AllocatedCount: 0, + AllocatedCapacity: 0, + Capacity: 0, + Count: 0, + }, + "secondCounter": { + AllocatedCount: 0, + AllocatedCapacity: 0, + Capacity: 0, + Count: 0, + }, + }, + } + + assert.Equal(t, expected, computeStatus(gsSet, list)) + }) + t.Run("lists", func(t *testing.T) { utilruntime.FeatureTestMutex.Lock() defer utilruntime.FeatureTestMutex.Unlock() @@ -484,7 +522,7 @@ func TestComputeStatus(t *testing.T) { ReadyReplicas: 0, ReservedReplicas: 0, AllocatedReplicas: 0, - Counters: nil, + Counters: map[string]agonesv1.AggregatedCounterStatus{}, Lists: map[string]agonesv1.AggregatedListStatus{ "firstList": { AllocatedCount: 0, diff --git a/test/e2e/fleetautoscaler_test.go b/test/e2e/fleetautoscaler_test.go index 78a164ea12..2ea0adc25f 100644 --- a/test/e2e/fleetautoscaler_test.go +++ b/test/e2e/fleetautoscaler_test.go @@ -944,6 +944,88 @@ func TestCounterAutoscaler(t *testing.T) { } } +// nolint:dupl // Linter errors on lines are duplicate of TestListAutoscalerWithNoReplicas +func TestCounterAutoscalerWithNoReplicas(t *testing.T) { + if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { + t.SkipNow() + } + t.Parallel() + + ctx := context.Background() + client := framework.AgonesClient.AgonesV1() + log := e2e.TestLogger(t) + + flt := defaultEmptyFleet(framework.Namespace) + flt.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{ + "games": { + Capacity: 5, + }, + } + + 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) + + counterFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler { + fas := autoscalingv1.FleetAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-counter-autoscaler", Namespace: framework.Namespace}, + Spec: autoscalingv1.FleetAutoscalerSpec{ + FleetName: flt.ObjectMeta.Name, + Policy: autoscalingv1.FleetAutoscalerPolicy{ + Type: autoscalingv1.CounterPolicyType, + }, + 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 Up to MinCapacity": { + fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) { + fap.Counter = &autoscalingv1.CounterPolicy{ + Key: "games", + BufferSize: intstr.FromInt(3), + MinCapacity: 16, + MaxCapacity: 100, + } + }), + wantFasErr: false, + wantReplicas: 4, // Capacity:20 + }, + } + 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) + + framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas)) + fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck + + // Return to starting 0 replicas + framework.ScaleFleet(t, log, flt, 0) + framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0)) + }) + } +} + func TestCounterAutoscalerAllocated(t *testing.T) { if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { t.SkipNow() @@ -1209,6 +1291,7 @@ func TestListAutoscaler(t *testing.T) { } } +// nolint:dupl // Linter errors on lines are duplicate of TestCounterAutoscalerWithNoReplicas func TestListAutoscalerWithNoReplicas(t *testing.T) { if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { t.SkipNow()