Skip to content

Commit

Permalink
Enable counter based autoscaler to scale from 0 replicas (#4049)
Browse files Browse the repository at this point in the history
* Enable counter based autoscaler to scale from 0 replicas

* Disabled linter on duplicate pattern for tests

* Fixing incorrect name for counter autoscaler test

---------

Co-authored-by: peterzhongyi <[email protected]>
  • Loading branch information
geopaulm and peterzhongyi authored Nov 28, 2024
1 parent 08bc4c0 commit cd9b7a1
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 2 deletions.
27 changes: 26 additions & 1 deletion pkg/fleetautoscalers/fleetautoscalers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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{
Expand Down
9 changes: 9 additions & 0 deletions pkg/gameserversets/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 39 additions & 1 deletion pkg/gameserversets/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down
83 changes: 83 additions & 0 deletions test/e2e/fleetautoscaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit cd9b7a1

Please sign in to comment.