agones.dev/agones@v1.54.0/pkg/gameserverallocations/find_test.go (about) 1 // Copyright 2019 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 gameserverallocations 16 17 import ( 18 "fmt" 19 "testing" 20 21 "agones.dev/agones/pkg/apis" 22 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 23 allocationv1 "agones.dev/agones/pkg/apis/allocation/v1" 24 agtesting "agones.dev/agones/pkg/testing" 25 "agones.dev/agones/pkg/util/runtime" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 k8sruntime "k8s.io/apimachinery/pkg/runtime" 30 k8stesting "k8s.io/client-go/testing" 31 ) 32 33 func TestFindGameServerForAllocationPacked(t *testing.T) { 34 t.Parallel() 35 36 oneLabel := map[string]string{"role": "gameserver"} 37 twoLabels := map[string]string{"role": "gameserver", "preferred": "true"} 38 39 gsa := &allocationv1.GameServerAllocation{ 40 ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs}, 41 Spec: allocationv1.GameServerAllocationSpec{ 42 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{ 43 MatchLabels: oneLabel, 44 }}}, 45 Scheduling: apis.Packed, 46 }, 47 } 48 49 n := metav1.Now() 50 twoLabelsGsa := gsa.DeepCopy() 51 twoLabelsGsa.Spec.Selectors = append( 52 []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{ 53 MatchLabels: map[string]string{"preferred": "true"}, 54 }}}, twoLabelsGsa.Spec.Selectors...) 55 56 fixtures := map[string]struct { 57 list []agonesv1.GameServer 58 test func(*testing.T, []*agonesv1.GameServer) 59 features string 60 gsa *allocationv1.GameServerAllocation 61 }{ 62 "empty selector": { 63 list: []agonesv1.GameServer{{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}}, 64 test: func(t *testing.T, list []*agonesv1.GameServer) { 65 require.Len(t, list, 1) 66 67 emptyGSA := &allocationv1.GameServerAllocation{ 68 ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs}, 69 Spec: allocationv1.GameServerAllocationSpec{ 70 Scheduling: apis.Packed, 71 }, 72 } 73 emptyGSA.ApplyDefaults() 74 emptyGSA.Converter() 75 allErrs := emptyGSA.Validate() 76 require.Len(t, allErrs, 0) 77 require.Len(t, emptyGSA.Spec.Selectors, 1) 78 79 gs, index, err := findGameServerForAllocation(emptyGSA, list) 80 assert.NotNil(t, gs) 81 assert.Equal(t, 0, index) 82 assert.NoError(t, err) 83 }, 84 }, 85 "one label with player state (StateAllocationFilter)": { 86 // nolint: dupl 87 list: []agonesv1.GameServer{ 88 {ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, Labels: oneLabel, DeletionTimestamp: &n}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 89 {ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 90 {ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 91 {ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}}, 92 {ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}}, 93 {ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateError}}, 94 {ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: "does-not-apply", Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 95 }, 96 test: func(t *testing.T, list []*agonesv1.GameServer) { 97 require.Len(t, list, 5) 98 require.Equal(t, agonesv1.GameServerStateReady, *gsa.Spec.Selectors[0].GameServerState) 99 100 gs, index, err := findGameServerForAllocation(gsa, list) 101 assert.NoError(t, err) 102 require.NotNil(t, gs) 103 assert.Equal(t, "node1", gs.Status.NodeName) 104 assert.Equal(t, "gs1", gs.ObjectMeta.Name) 105 assert.Equal(t, gs, list[index]) 106 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 107 108 // remove the allocated game server 109 list = append(list[:index], list[index+1:]...) 110 assert.Len(t, list, 4) 111 112 gs, index, err = findGameServerForAllocation(gsa, list) 113 assert.NoError(t, err) 114 require.NotNil(t, gs) 115 116 assert.Equal(t, "node2", gs.Status.NodeName) 117 assert.Equal(t, "gs2", gs.ObjectMeta.Name) 118 assert.Equal(t, gs, list[index]) 119 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 120 121 // now try an allocated state 122 gsa := gsa.DeepCopy() 123 allocated := agonesv1.GameServerStateAllocated 124 gsa.Spec.Selectors[0].GameServerState = &allocated 125 126 gs, index, err = findGameServerForAllocation(gsa, list) 127 assert.NoError(t, err) 128 require.NotNil(t, gs) 129 assert.Equal(t, "node1", gs.Status.NodeName) 130 // either is valid 131 assert.Contains(t, []string{"gs3", "gs4"}, gs.ObjectMeta.Name) 132 assert.Equal(t, gs, list[index]) 133 assert.Equal(t, allocated, gs.Status.State) 134 135 // finally, we have nothing left 136 list = nil 137 gs, _, err = findGameServerForAllocation(gsa, list) 138 assert.Error(t, err) 139 assert.Equal(t, ErrNoGameServer, err) 140 assert.Nil(t, gs) 141 }, 142 features: fmt.Sprintf("%s=true", runtime.FeaturePlayerAllocationFilter), 143 }, 144 "one label with player counts and state (PlayerAllocationFilter)": { 145 list: []agonesv1.GameServer{ 146 {ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 147 {ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 148 {ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: oneLabel}, 149 Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated, 150 Players: &agonesv1.PlayerStatus{Count: 10, Capacity: 15}}}, 151 {ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: oneLabel}, 152 Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated, Players: &agonesv1.PlayerStatus{ 153 Count: 3, 154 Capacity: 15, 155 }}}, 156 }, 157 test: func(t *testing.T, list []*agonesv1.GameServer) { 158 gsa := gsa.DeepCopy() 159 allocated := agonesv1.GameServerStateAllocated 160 gsa.Spec.Selectors[0].GameServerState = &allocated 161 gsa.Spec.Selectors[0].Players = &allocationv1.PlayerSelector{ 162 MinAvailable: 1, 163 MaxAvailable: 10, 164 } 165 require.Len(t, list, 4) 166 167 gs, index, err := findGameServerForAllocation(gsa, list) 168 assert.NoError(t, err) 169 require.NotNil(t, gs) 170 assert.Equal(t, "node1", gs.Status.NodeName) 171 assert.Equal(t, "gs3", gs.ObjectMeta.Name) 172 assert.Equal(t, gs, list[index]) 173 assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State) 174 }, 175 features: fmt.Sprintf("%s=true", runtime.FeaturePlayerAllocationFilter), 176 }, 177 "preferred": { 178 list: []agonesv1.GameServer{ 179 {ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: twoLabels}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 180 {ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 181 {ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 182 {ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: twoLabels}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 183 {ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 184 {ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 185 }, 186 test: func(t *testing.T, list []*agonesv1.GameServer) { 187 assert.Len(t, list, 6) 188 189 gs, index, err := findGameServerForAllocation(twoLabelsGsa, list) 190 assert.NoError(t, err) 191 assert.Equal(t, "node1", gs.Status.NodeName) 192 assert.Equal(t, "gs1", gs.ObjectMeta.Name) 193 assert.Equal(t, gs, list[index]) 194 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 195 196 list = append(list[:index], list[index+1:]...) 197 gs, index, err = findGameServerForAllocation(twoLabelsGsa, list) 198 assert.NoError(t, err) 199 assert.Equal(t, "node2", gs.Status.NodeName) 200 assert.Equal(t, "gs4", gs.ObjectMeta.Name) 201 assert.Equal(t, gs, list[index]) 202 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 203 204 list = append(list[:index], list[index+1:]...) 205 gs, index, err = findGameServerForAllocation(twoLabelsGsa, list) 206 assert.NoError(t, err) 207 assert.Equal(t, "node1", gs.Status.NodeName) 208 assert.Contains(t, []string{"gs3", "gs5", "gs6"}, gs.ObjectMeta.Name) 209 assert.Equal(t, gs, list[index]) 210 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 211 }, 212 features: fmt.Sprintf("%s=false", runtime.FeaturePlayerAllocationFilter), 213 }, 214 "allocation trap": { 215 list: []agonesv1.GameServer{ 216 {ObjectMeta: metav1.ObjectMeta{Name: "gs1", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}}, 217 {ObjectMeta: metav1.ObjectMeta{Name: "gs2", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}}, 218 {ObjectMeta: metav1.ObjectMeta{Name: "gs3", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}}, 219 {ObjectMeta: metav1.ObjectMeta{Name: "gs4", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}}, 220 {ObjectMeta: metav1.ObjectMeta{Name: "gs5", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 221 {ObjectMeta: metav1.ObjectMeta{Name: "gs6", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 222 {ObjectMeta: metav1.ObjectMeta{Name: "gs7", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 223 {ObjectMeta: metav1.ObjectMeta{Name: "gs8", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 224 }, 225 test: func(t *testing.T, list []*agonesv1.GameServer) { 226 assert.Len(t, list, 8) 227 228 gs, index, err := findGameServerForAllocation(gsa, list) 229 assert.Nil(t, err) 230 assert.Equal(t, "node2", gs.Status.NodeName) 231 assert.Equal(t, gs, list[index]) 232 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 233 }, 234 features: fmt.Sprintf("%s=false", runtime.FeaturePlayerAllocationFilter), 235 }, 236 } 237 238 for k, v := range fixtures { 239 t.Run(k, func(t *testing.T) { 240 runtime.FeatureTestMutex.Lock() 241 defer runtime.FeatureTestMutex.Unlock() 242 // we always set the feature flag in all these tests, so always process it. 243 require.NoError(t, runtime.ParseFeatures(v.features)) 244 245 gsa.ApplyDefaults() 246 allErrs := gsa.Validate() 247 require.Len(t, allErrs, 0) 248 249 twoLabelsGsa.ApplyDefaults() 250 allErrs = twoLabelsGsa.Validate() 251 require.Len(t, allErrs, 0) 252 253 controller, m := newFakeController() 254 c := controller.allocator.allocationCache 255 256 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 257 return true, &agonesv1.GameServerList{Items: v.list}, nil 258 }) 259 260 ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced) 261 defer cancel() 262 263 // This call initializes the cache 264 err := c.syncCache() 265 assert.Nil(t, err) 266 267 err = c.counter.Run(ctx, 0) 268 assert.Nil(t, err) 269 270 list := c.ListSortedGameServers(v.gsa) 271 v.test(t, list) 272 }) 273 } 274 } 275 276 func TestFindGameServerForAllocationDistributed(t *testing.T) { 277 t.Parallel() 278 279 // TODO: remove when `CountsAndLists` feature flag is moved to stable. 280 // NOTE: CountsAndLists has different behavior for Distributed, and the game server list is not random. 281 runtime.FeatureTestMutex.Lock() 282 defer runtime.FeatureTestMutex.Unlock() 283 assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=false")) 284 285 controller, m := newFakeController() 286 c := controller.allocator.allocationCache 287 labels := map[string]string{"role": "gameserver"} 288 289 gsa := &allocationv1.GameServerAllocation{ 290 ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs}, 291 Spec: allocationv1.GameServerAllocationSpec{ 292 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{ 293 MatchLabels: labels, 294 }}}, 295 Scheduling: apis.Distributed, 296 }, 297 } 298 gsa.ApplyDefaults() 299 allErrs := gsa.Validate() 300 require.Len(t, allErrs, 0) 301 302 gsList := []agonesv1.GameServer{ 303 {ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: labels}, 304 Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 305 {ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: labels}, 306 Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 307 {ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: labels}, 308 Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}, 309 {ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: labels}, 310 Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateError}}, 311 {ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, Labels: labels}, 312 Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 313 {ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, Labels: labels}, 314 Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}}, 315 {ObjectMeta: metav1.ObjectMeta{Name: "gs7", Namespace: defaultNs, Labels: labels}, 316 Status: agonesv1.GameServerStatus{NodeName: "node3", State: agonesv1.GameServerStateReady}}, 317 } 318 319 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 320 return true, &agonesv1.GameServerList{Items: gsList}, nil 321 }) 322 323 ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced) 324 defer cancel() 325 326 // This call initializes the cache 327 err := c.syncCache() 328 assert.Nil(t, err) 329 330 err = c.counter.Run(ctx, 0) 331 assert.Nil(t, err) 332 333 list := c.ListSortedGameServers(gsa) 334 assert.Len(t, list, 6) 335 336 gs, index, err := findGameServerForAllocation(gsa, list) 337 assert.NoError(t, err) 338 assert.Equal(t, gs, list[index]) 339 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 340 341 past := gs 342 // we should get a different result in 10 tries, so we can see we get some randomness. 343 for i := 0; i < 10; i++ { 344 gs, index, err = findGameServerForAllocation(gsa, list) 345 assert.NoError(t, err) 346 assert.Equal(t, gs, list[index]) 347 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 348 349 if gs.ObjectMeta.Name != past.ObjectMeta.Name { 350 return 351 } 352 } 353 354 assert.FailNow(t, "We should get a different gameserver by now") 355 356 }