Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable counter based autoscaler to scale from 0 replicas #4049

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 + "-list-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