agones.dev/agones@v1.54.0/pkg/gameserversets/allocation_overflow_test.go (about) 1 // Copyright 2023 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 gameserversets 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 24 "agones.dev/agones/pkg/gameservers" 25 agtesting "agones.dev/agones/pkg/testing" 26 "github.com/heptiolabs/healthcheck" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/watch" 31 k8stesting "k8s.io/client-go/testing" 32 ) 33 34 func TestAllocationOverflowControllerWatchGameServers(t *testing.T) { 35 t.Parallel() 36 37 gsSet := defaultFixture() 38 gsSet.Status.Replicas = gsSet.Spec.Replicas 39 gsSet.Status.ReadyReplicas = gsSet.Spec.Replicas 40 c, m := newFakeAllocationOverflowController() 41 42 received := make(chan string, 10) 43 defer close(received) 44 45 gsSetWatch := watch.NewFake() 46 m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil)) 47 48 c.workerqueue.SyncHandler = func(_ context.Context, name string) error { 49 received <- name 50 return nil 51 } 52 53 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced) 54 defer cancel() 55 56 go func() { 57 err := c.Run(ctx) 58 require.NoError(t, err) 59 }() 60 61 change := func() string { 62 select { 63 case result := <-received: 64 return result 65 case <-time.After(3 * time.Second): 66 require.FailNow(t, "timeout occurred") 67 } 68 return "" 69 } 70 71 nochange := func() { 72 select { 73 case <-received: 74 assert.Fail(t, "Should be no value") 75 case <-time.After(time.Second): 76 } 77 } 78 79 gsSetWatch.Add(gsSet.DeepCopy()) 80 nochange() 81 82 // update with no allocation overflow 83 require.Nil(t, gsSet.Spec.AllocationOverflow) 84 gsSet.Spec.Replicas++ 85 gsSetWatch.Modify(gsSet.DeepCopy()) 86 nochange() 87 88 // update with no labels or annotations 89 gsSet.Spec.AllocationOverflow = &agonesv1.AllocationOverflow{} 90 gsSet.Spec.Replicas++ 91 gsSetWatch.Modify(gsSet.DeepCopy()) 92 nochange() 93 94 // update with allocation <= replicas (and a label) 95 gsSet.Spec.AllocationOverflow.Labels = map[string]string{"colour": "green"} 96 gsSet.Status.AllocatedReplicas = 2 97 gsSetWatch.Modify(gsSet.DeepCopy()) 98 nochange() 99 100 // update with allocation > replicas 101 gsSet.Status.AllocatedReplicas = 20 102 gsSetWatch.Modify(gsSet.DeepCopy()) 103 require.Equal(t, fmt.Sprintf("%s/%s", gsSet.ObjectMeta.Namespace, gsSet.ObjectMeta.Name), change()) 104 105 // delete 106 gsSetWatch.Delete(gsSet.DeepCopy()) 107 nochange() 108 } 109 110 func TestAllocationOverflowSyncGameServerSet(t *testing.T) { 111 t.Parallel() 112 113 // setup fictures. 114 setup := func(gs func(server *agonesv1.GameServer)) (*agonesv1.GameServerSet, *AllocationOverflowController, agtesting.Mocks) { 115 gsSet := defaultFixture() 116 gsSet.Status.AllocatedReplicas = 5 117 gsSet.Status.Replicas = 3 118 gsSet.Spec.Replicas = 3 119 gsSet.Spec.AllocationOverflow = &agonesv1.AllocationOverflow{Labels: map[string]string{"colour": "green"}} 120 list := createGameServers(gsSet, 5) 121 for i := range list { 122 list[i].Status.State = agonesv1.GameServerStateAllocated 123 gs(&list[i]) 124 } 125 126 c, m := newFakeAllocationOverflowController() 127 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 128 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 129 }) 130 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 131 return true, &agonesv1.GameServerList{Items: list}, nil 132 }) 133 return gsSet, c, m 134 } 135 136 // run the sync process 137 run := func(c *AllocationOverflowController, m agtesting.Mocks, gsSet *agonesv1.GameServerSet, update func(action k8stesting.Action) (bool, runtime.Object, error)) func() { 138 m.AgonesClient.AddReactor("update", "gameservers", update) 139 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 140 err := c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) 141 require.NoError(t, err) 142 return cancel 143 } 144 145 t.Run("labels are applied", func(t *testing.T) { 146 gsSet, c, m := setup(func(_ *agonesv1.GameServer) {}) 147 count := 0 148 cancel := run(c, m, gsSet, func(action k8stesting.Action) (bool, runtime.Object, error) { 149 ua := action.(k8stesting.UpdateAction) 150 gs := ua.GetObject().(*agonesv1.GameServer) 151 require.Equal(t, gs.Status.State, agonesv1.GameServerStateAllocated) 152 require.Equal(t, "green", gs.ObjectMeta.Labels["colour"]) 153 154 count++ 155 return true, nil, nil 156 }) 157 defer cancel() 158 require.Equal(t, 2, count) 159 }) 160 161 t.Run("Labels are already set", func(t *testing.T) { 162 gsSet, c, m := setup(func(gs *agonesv1.GameServer) { 163 gs.ObjectMeta.Labels["colour"] = "green" 164 }) 165 cancel := run(c, m, gsSet, func(_ k8stesting.Action) (bool, runtime.Object, error) { 166 require.Fail(t, "should not update") 167 return true, nil, nil 168 }) 169 defer cancel() 170 }) 171 172 t.Run("one label is set", func(t *testing.T) { 173 set := false 174 gsSet, c, m := setup(func(gs *agonesv1.GameServer) { 175 // just make one as already set 176 if !set { 177 gs.ObjectMeta.Labels["colour"] = "green" 178 set = true 179 } 180 }) 181 182 count := 0 183 cancel := run(c, m, gsSet, func(action k8stesting.Action) (bool, runtime.Object, error) { 184 ua := action.(k8stesting.UpdateAction) 185 gs := ua.GetObject().(*agonesv1.GameServer) 186 require.Equal(t, gs.Status.State, agonesv1.GameServerStateAllocated) 187 require.Equal(t, "green", gs.ObjectMeta.Labels["colour"]) 188 189 count++ 190 return true, nil, nil 191 }) 192 defer cancel() 193 require.Equal(t, 1, count) 194 }) 195 } 196 197 // newFakeAllocationOverflowController returns a controller, backed by the fake Clientset 198 func newFakeAllocationOverflowController() (*AllocationOverflowController, agtesting.Mocks) { 199 m := agtesting.NewMocks() 200 counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory) 201 c := NewAllocatorOverflowController(healthcheck.NewHandler(), counter, m.AgonesClient, m.AgonesInformerFactory) 202 return c, m 203 }