agones.dev/agones@v1.54.0/pkg/gameserverallocations/controller_test.go (about) 1 // Copyright 2018 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 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "net/http" 23 "net/http/httptest" 24 "strconv" 25 "testing" 26 "time" 27 28 pb "agones.dev/agones/pkg/allocation/go" 29 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 30 allocationv1 "agones.dev/agones/pkg/apis/allocation/v1" 31 multiclusterv1 "agones.dev/agones/pkg/apis/multicluster/v1" 32 "agones.dev/agones/pkg/gameservers" 33 agtesting "agones.dev/agones/pkg/testing" 34 "agones.dev/agones/pkg/util/apiserver" 35 "github.com/heptiolabs/healthcheck" 36 "github.com/pkg/errors" 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 "google.golang.org/grpc" 40 "google.golang.org/grpc/codes" 41 "google.golang.org/grpc/status" 42 corev1 "k8s.io/api/core/v1" 43 k8serrors "k8s.io/apimachinery/pkg/api/errors" 44 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 45 k8sruntime "k8s.io/apimachinery/pkg/runtime" 46 "k8s.io/apimachinery/pkg/util/wait" 47 "k8s.io/apimachinery/pkg/watch" 48 k8stesting "k8s.io/client-go/testing" 49 ) 50 51 const ( 52 defaultNs = "default" 53 n1 = "node1" 54 n2 = "node2" 55 56 unhealthyEndpoint = "unhealthy_endpoint:443" 57 ) 58 59 func TestControllerAllocator(t *testing.T) { 60 t.Parallel() 61 62 t.Run("successful allocation", func(t *testing.T) { 63 f, gsList := defaultFixtures(4) 64 65 gsaSelectors := &allocationv1.GameServerAllocation{ 66 Spec: allocationv1.GameServerAllocationSpec{ 67 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}}, 68 }} 69 gsaRequired := &allocationv1.GameServerAllocation{ 70 Spec: allocationv1.GameServerAllocationSpec{ 71 Required: allocationv1.GameServerSelector{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}, 72 }} 73 74 c, m := newFakeController() 75 gsWatch := watch.NewFake() 76 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 77 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 78 return true, &agonesv1.GameServerList{Items: gsList}, nil 79 }) 80 81 updated := map[string]bool{} 82 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) { 83 ua := action.(k8stesting.UpdateAction) 84 gs := ua.GetObject().(*agonesv1.GameServer) 85 86 if _, ok := updated[gs.ObjectMeta.Name]; ok { 87 return true, nil, k8serrors.NewConflict(agonesv1.Resource("gameservers"), gs.ObjectMeta.Name, fmt.Errorf("already updated")) 88 } 89 90 updated[gs.ObjectMeta.Name] = true 91 gsWatch.Modify(gs) 92 return true, gs, nil 93 }) 94 95 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 96 defer cancel() 97 98 if err := c.Run(ctx, 1); err != nil { 99 assert.FailNow(t, err.Error()) 100 } 101 // wait for it to be up and running 102 err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) { 103 return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil 104 }) 105 assert.NoError(t, err) 106 107 test := func(gsa *allocationv1.GameServerAllocation, expectedState allocationv1.GameServerAllocationState) { 108 buf := bytes.NewBuffer(nil) 109 err := json.NewEncoder(buf).Encode(gsa) 110 require.NoError(t, err) 111 r, err := http.NewRequest(http.MethodPost, "/", buf) 112 r.Header.Set("Content-Type", k8sruntime.ContentTypeJSON) 113 require.NoError(t, err) 114 rec := httptest.NewRecorder() 115 err = c.processAllocationRequest(ctx, rec, r, "default") 116 require.NoError(t, err) 117 require.Equal(t, http.StatusCreated, rec.Code) 118 ret := &allocationv1.GameServerAllocation{} 119 err = json.Unmarshal(rec.Body.Bytes(), ret) 120 require.NoError(t, err) 121 122 if len(gsa.Spec.Selectors) != 0 { 123 require.Equal(t, gsa.Spec.Selectors[0].LabelSelector, ret.Spec.Selectors[0].LabelSelector) 124 } else { 125 // nolint:staticcheck 126 require.Equal(t, gsa.Spec.Required.LabelSelector, ret.Spec.Selectors[0].LabelSelector) 127 } 128 129 require.True(t, expectedState == ret.Status.State, "Failed: %s vs %s", expectedState, ret.Status.State) 130 } 131 132 test(gsaSelectors.DeepCopy(), allocationv1.GameServerAllocationAllocated) 133 test(gsaRequired.DeepCopy(), allocationv1.GameServerAllocationAllocated) 134 test(gsaSelectors.DeepCopy(), allocationv1.GameServerAllocationAllocated) 135 test(gsaRequired.DeepCopy(), allocationv1.GameServerAllocationAllocated) 136 test(gsaSelectors.DeepCopy(), allocationv1.GameServerAllocationUnAllocated) 137 test(gsaRequired.DeepCopy(), allocationv1.GameServerAllocationUnAllocated) 138 }) 139 140 t.Run("method not allowed", func(t *testing.T) { 141 c, _ := newFakeController() 142 r, err := http.NewRequest(http.MethodGet, "/", nil) 143 rec := httptest.NewRecorder() 144 assert.NoError(t, err) 145 146 err = c.processAllocationRequest(context.Background(), rec, r, "default") 147 assert.NoError(t, err) 148 149 assert.Equal(t, http.StatusMethodNotAllowed, rec.Code) 150 }) 151 152 t.Run("invalid gameserverallocation", func(t *testing.T) { 153 c, _ := newFakeController() 154 gsa := &allocationv1.GameServerAllocation{ 155 Spec: allocationv1.GameServerAllocationSpec{ 156 Scheduling: "wrong", 157 }} 158 buf := bytes.NewBuffer(nil) 159 err := json.NewEncoder(buf).Encode(gsa) 160 assert.NoError(t, err) 161 r, err := http.NewRequest(http.MethodPost, "/", buf) 162 r.Header.Set("Content-Type", k8sruntime.ContentTypeJSON) 163 assert.NoError(t, err) 164 rec := httptest.NewRecorder() 165 err = c.processAllocationRequest(context.Background(), rec, r, "default") 166 assert.NoError(t, err) 167 168 assert.Equal(t, http.StatusUnprocessableEntity, rec.Code) 169 170 s := &metav1.Status{} 171 err = json.NewDecoder(rec.Body).Decode(s) 172 assert.NoError(t, err) 173 174 assert.Equal(t, metav1.StatusReasonInvalid, s.Reason) 175 }) 176 } 177 178 func TestAllocationApiResource(t *testing.T) { 179 t.Parallel() 180 181 c, m := newFakeController() 182 c.registerAPIResource(context.Background()) 183 184 ts := httptest.NewServer(m.Mux) 185 defer ts.Close() 186 187 client := ts.Client() 188 189 resp, err := client.Get(ts.URL + "/apis/" + allocationv1.SchemeGroupVersion.String()) 190 if !assert.Nil(t, err) { 191 assert.FailNow(t, err.Error()) 192 } 193 defer resp.Body.Close() // nolint: errcheck 194 195 list := &metav1.APIResourceList{} 196 err = json.NewDecoder(resp.Body).Decode(list) 197 assert.Nil(t, err) 198 199 if assert.Len(t, list.APIResources, 1) { 200 assert.Equal(t, "gameserverallocation", list.APIResources[0].SingularName) 201 } 202 } 203 204 func TestMultiClusterAllocationFromLocal(t *testing.T) { 205 t.Parallel() 206 t.Run("Handle allocation request locally", func(t *testing.T) { 207 c, m := newFakeController() 208 fleetName := addReactorForGameServer(&m) 209 210 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 211 return true, &multiclusterv1.GameServerAllocationPolicyList{ 212 Items: []multiclusterv1.GameServerAllocationPolicy{ 213 { 214 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 215 Priority: 1, 216 Weight: 200, 217 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 218 ClusterName: "multicluster", 219 SecretName: "localhostsecret", 220 Namespace: defaultNs, 221 ServerCA: []byte("not-used"), 222 }, 223 }, 224 ObjectMeta: metav1.ObjectMeta{ 225 Labels: map[string]string{"cluster": "onprem"}, 226 Namespace: defaultNs, 227 }, 228 }, 229 }, 230 }, nil 231 }) 232 233 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 234 defer cancel() 235 236 if err := c.Run(ctx, 1); err != nil { 237 assert.FailNow(t, err.Error()) 238 } 239 // wait for it to be up and running 240 err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) { 241 return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil 242 }) 243 assert.NoError(t, err) 244 245 gsa := &allocationv1.GameServerAllocation{ 246 ObjectMeta: metav1.ObjectMeta{ 247 Namespace: defaultNs, 248 Name: "alloc1", 249 }, 250 Spec: allocationv1.GameServerAllocationSpec{ 251 MultiClusterSetting: allocationv1.MultiClusterSetting{ 252 Enabled: true, 253 PolicySelector: metav1.LabelSelector{ 254 MatchLabels: map[string]string{ 255 "cluster": "onprem", 256 }, 257 }, 258 }, 259 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 260 }, 261 } 262 263 ret, err := executeAllocation(gsa, c) 264 require.NoError(t, err) 265 assert.Equal(t, gsa.Spec.Selectors[0].LabelSelector, ret.Spec.Selectors[0].LabelSelector) 266 assert.Equal(t, gsa.Namespace, ret.Namespace) 267 expectedState := allocationv1.GameServerAllocationAllocated 268 assert.True(t, expectedState == ret.Status.State, "Failed: %s vs %s", expectedState, ret.Status.State) 269 }) 270 271 t.Run("Missing multicluster policy", func(t *testing.T) { 272 c, m := newFakeController() 273 fleetName := addReactorForGameServer(&m) 274 275 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 276 return true, &multiclusterv1.GameServerAllocationPolicyList{ 277 Items: []multiclusterv1.GameServerAllocationPolicy{}, 278 }, nil 279 }) 280 281 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 282 defer cancel() 283 284 if err := c.Run(ctx, 1); err != nil { 285 assert.FailNow(t, err.Error()) 286 } 287 // wait for it to be up and running 288 err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) { 289 return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil 290 }) 291 assert.NoError(t, err) 292 293 gsa := &allocationv1.GameServerAllocation{ 294 ObjectMeta: metav1.ObjectMeta{ 295 Namespace: defaultNs, 296 Name: "alloc1", 297 }, 298 Spec: allocationv1.GameServerAllocationSpec{ 299 MultiClusterSetting: allocationv1.MultiClusterSetting{ 300 Enabled: true, 301 PolicySelector: metav1.LabelSelector{ 302 MatchLabels: map[string]string{ 303 "cluster": "onprem", 304 }, 305 }, 306 }, 307 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 308 }, 309 } 310 311 _, err = executeAllocation(gsa, c) 312 assert.Error(t, err) 313 }) 314 315 t.Run("Could not find a Ready GameServer", func(t *testing.T) { 316 c, m := newFakeController() 317 318 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 319 return true, &multiclusterv1.GameServerAllocationPolicyList{ 320 Items: []multiclusterv1.GameServerAllocationPolicy{ 321 { 322 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 323 Priority: 1, 324 Weight: 200, 325 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 326 ClusterName: "multicluster", 327 SecretName: "localhostsecret", 328 Namespace: defaultNs, 329 ServerCA: []byte("not-used"), 330 }, 331 }, 332 ObjectMeta: metav1.ObjectMeta{ 333 Labels: map[string]string{"cluster": "onprem"}, 334 Namespace: defaultNs, 335 }, 336 }, 337 }, 338 }, nil 339 }) 340 341 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 342 defer cancel() 343 344 if err := c.Run(ctx, 1); err != nil { 345 assert.FailNow(t, err.Error()) 346 } 347 // wait for it to be up and running 348 err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) { 349 return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil 350 }) 351 assert.NoError(t, err) 352 353 gsa := &allocationv1.GameServerAllocation{ 354 ObjectMeta: metav1.ObjectMeta{ 355 Namespace: defaultNs, 356 Name: "alloc1", 357 }, 358 Spec: allocationv1.GameServerAllocationSpec{ 359 MultiClusterSetting: allocationv1.MultiClusterSetting{ 360 Enabled: true, 361 PolicySelector: metav1.LabelSelector{ 362 MatchLabels: map[string]string{ 363 "cluster": "onprem", 364 }, 365 }, 366 }, 367 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: "empty-fleet"}}}}, 368 }, 369 } 370 371 ret, err := executeAllocation(gsa, c) 372 assert.NoError(t, err) 373 assert.Equal(t, gsa.Spec.Selectors[0].LabelSelector, ret.Spec.Selectors[0].LabelSelector) 374 assert.Equal(t, gsa.Namespace, ret.Namespace) 375 expectedState := allocationv1.GameServerAllocationUnAllocated 376 assert.True(t, expectedState == ret.Status.State, "Failed: %s vs %s", expectedState, ret.Status.State) 377 }) 378 } 379 380 func TestMultiClusterAllocationFromRemote(t *testing.T) { 381 const clusterName = "remotecluster" 382 t.Parallel() 383 t.Run("Handle allocation request remotely", func(t *testing.T) { 384 c, m := newFakeController() 385 fleetName := addReactorForGameServer(&m) 386 expectedGSName := "mocked" 387 endpoint := "x.x.x.x" 388 389 // Allocation policy reactor 390 secretName := clusterName + "secret" 391 targetedNamespace := "tns" 392 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 393 return true, &multiclusterv1.GameServerAllocationPolicyList{ 394 Items: []multiclusterv1.GameServerAllocationPolicy{ 395 { 396 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 397 Priority: 1, 398 Weight: 200, 399 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 400 AllocationEndpoints: []string{endpoint, "non-existing"}, 401 ClusterName: clusterName, 402 SecretName: secretName, 403 Namespace: targetedNamespace, 404 ServerCA: clientCert, 405 }, 406 }, 407 ObjectMeta: metav1.ObjectMeta{ 408 Namespace: defaultNs, 409 }, 410 }, 411 }, 412 }, nil 413 }) 414 415 m.KubeClient.AddReactor("list", "secrets", 416 func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 417 return true, getTestSecret(secretName, nil), nil 418 }) 419 420 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 421 defer cancel() 422 423 // This call initializes the cache 424 err := c.allocator.allocationCache.syncCache() 425 assert.Nil(t, err) 426 427 err = c.allocator.allocationCache.counter.Run(ctx, 0) 428 assert.Nil(t, err) 429 430 gsa := &allocationv1.GameServerAllocation{ 431 ObjectMeta: metav1.ObjectMeta{ 432 Namespace: defaultNs, 433 Name: "alloc1", 434 }, 435 Spec: allocationv1.GameServerAllocationSpec{ 436 MultiClusterSetting: allocationv1.MultiClusterSetting{ 437 Enabled: true, 438 }, 439 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 440 }, 441 } 442 443 c.allocator.remoteAllocationCallback = func(_ context.Context, e string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) { 444 assert.Equal(t, endpoint+":443", e) 445 serverResponse := pb.AllocationResponse{ 446 GameServerName: expectedGSName, 447 } 448 return &serverResponse, nil 449 } 450 451 result, err := executeAllocation(gsa, c) 452 if assert.NoError(t, err) { 453 assert.Equal(t, expectedGSName, result.Status.GameServerName) 454 } 455 }) 456 457 t.Run("Remote server returns conflict and then random error", func(t *testing.T) { 458 c, m := newFakeController() 459 fleetName := addReactorForGameServer(&m) 460 461 // Mock server to return unallocated and then error 462 count := 0 463 retry := 0 464 endpoint := "z.z.z.z" 465 466 c.allocator.remoteAllocationCallback = func(_ context.Context, _ string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) { 467 if count == 0 { 468 serverResponse := pb.AllocationResponse{} 469 count++ 470 return &serverResponse, status.Error(codes.Aborted, "conflict") 471 } 472 473 retry++ 474 return nil, errors.New("test error message") 475 } 476 477 // Allocation policy reactor 478 secretName := clusterName + "secret" 479 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 480 return true, &multiclusterv1.GameServerAllocationPolicyList{ 481 Items: []multiclusterv1.GameServerAllocationPolicy{ 482 { 483 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 484 Priority: 1, 485 Weight: 200, 486 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 487 AllocationEndpoints: []string{endpoint}, 488 ClusterName: clusterName, 489 SecretName: secretName, 490 ServerCA: clientCert, 491 }, 492 }, 493 ObjectMeta: metav1.ObjectMeta{ 494 Name: "name1", 495 Namespace: defaultNs, 496 }, 497 }, 498 { 499 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 500 Priority: 2, 501 Weight: 200, 502 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 503 AllocationEndpoints: []string{endpoint}, 504 ClusterName: "remotecluster2", 505 SecretName: secretName, 506 ServerCA: clientCert, 507 }, 508 }, 509 ObjectMeta: metav1.ObjectMeta{ 510 Name: "name2", 511 Namespace: defaultNs, 512 }, 513 }, 514 }, 515 }, nil 516 }) 517 518 m.KubeClient.AddReactor("list", "secrets", 519 func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 520 return true, getTestSecret(secretName, clientCert), nil 521 }) 522 523 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 524 defer cancel() 525 526 // This call initializes the cache 527 err := c.allocator.allocationCache.syncCache() 528 assert.Nil(t, err) 529 530 err = c.allocator.allocationCache.counter.Run(ctx, 0) 531 assert.Nil(t, err) 532 533 gsa := &allocationv1.GameServerAllocation{ 534 ObjectMeta: metav1.ObjectMeta{ 535 Namespace: defaultNs, 536 Name: "alloc1", 537 }, 538 Spec: allocationv1.GameServerAllocationSpec{ 539 MultiClusterSetting: allocationv1.MultiClusterSetting{ 540 Enabled: true, 541 }, 542 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 543 }, 544 } 545 546 _, err = executeAllocation(gsa, c) 547 if assert.Error(t, err) { 548 assert.Contains(t, err.Error(), "test error message") 549 } 550 assert.Truef(t, retry > 1, "Retry count %v. Expecting to retry on error.", retry) 551 }) 552 553 t.Run("First server fails and second server succeeds", func(t *testing.T) { 554 c, m := newFakeController() 555 fleetName := addReactorForGameServer(&m) 556 557 healthyEndpoint := "healthy_endpoint:443" 558 559 expectedGSName := "mocked" 560 c.allocator.remoteAllocationCallback = func(_ context.Context, endpoint string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) { 561 if endpoint == unhealthyEndpoint { 562 return nil, errors.New("test error message") 563 } 564 565 assert.Equal(t, healthyEndpoint, endpoint) 566 serverResponse := pb.AllocationResponse{ 567 GameServerName: expectedGSName, 568 } 569 return &serverResponse, nil 570 } 571 572 // Allocation policy reactor 573 secretName := clusterName + "secret" 574 575 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 576 return true, &multiclusterv1.GameServerAllocationPolicyList{ 577 Items: []multiclusterv1.GameServerAllocationPolicy{ 578 { 579 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 580 Priority: 1, 581 Weight: 200, 582 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 583 AllocationEndpoints: []string{unhealthyEndpoint, healthyEndpoint}, 584 ClusterName: clusterName, 585 SecretName: secretName, 586 ServerCA: clientCert, 587 }, 588 }, 589 ObjectMeta: metav1.ObjectMeta{ 590 Namespace: defaultNs, 591 }, 592 }, 593 }, 594 }, nil 595 }) 596 597 m.KubeClient.AddReactor("list", "secrets", 598 func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 599 return true, getTestSecret(secretName, clientCert), nil 600 }) 601 602 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 603 defer cancel() 604 605 // This call initializes the cache 606 err := c.allocator.allocationCache.syncCache() 607 assert.Nil(t, err) 608 609 err = c.allocator.allocationCache.counter.Run(ctx, 0) 610 assert.Nil(t, err) 611 612 gsa := &allocationv1.GameServerAllocation{ 613 ObjectMeta: metav1.ObjectMeta{ 614 Namespace: defaultNs, 615 Name: "alloc1", 616 }, 617 Spec: allocationv1.GameServerAllocationSpec{ 618 MultiClusterSetting: allocationv1.MultiClusterSetting{ 619 Enabled: true, 620 }, 621 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 622 }, 623 } 624 625 result, err := executeAllocation(gsa, c) 626 if assert.NoError(t, err) { 627 assert.Equal(t, expectedGSName, result.Status.GameServerName) 628 } 629 }) 630 t.Run("No allocations called after total timeout", func(t *testing.T) { 631 c, m := newFakeControllerWithTimeout(10*time.Second, 0*time.Second) 632 fleetName := addReactorForGameServer(&m) 633 634 calls := 0 635 c.allocator.remoteAllocationCallback = func(_ context.Context, _ string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) { 636 calls++ 637 return nil, errors.New("Error") 638 } 639 640 // Allocation policy reactor 641 secretName := clusterName + "secret" 642 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 643 return true, &multiclusterv1.GameServerAllocationPolicyList{ 644 Items: []multiclusterv1.GameServerAllocationPolicy{ 645 { 646 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 647 Priority: 1, 648 Weight: 200, 649 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 650 AllocationEndpoints: []string{unhealthyEndpoint}, 651 ClusterName: clusterName, 652 SecretName: secretName, 653 ServerCA: clientCert, 654 }, 655 }, 656 ObjectMeta: metav1.ObjectMeta{ 657 Namespace: defaultNs, 658 }, 659 }, 660 }, 661 }, nil 662 }) 663 664 m.KubeClient.AddReactor("list", "secrets", 665 func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 666 return true, getTestSecret(secretName, clientCert), nil 667 }) 668 669 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 670 defer cancel() 671 672 // This call initializes the cache 673 err := c.allocator.allocationCache.syncCache() 674 assert.Nil(t, err) 675 676 err = c.allocator.allocationCache.counter.Run(ctx, 0) 677 assert.Nil(t, err) 678 679 gsa := &allocationv1.GameServerAllocation{ 680 ObjectMeta: metav1.ObjectMeta{ 681 Namespace: defaultNs, 682 Name: "alloc1", 683 }, 684 Spec: allocationv1.GameServerAllocationSpec{ 685 MultiClusterSetting: allocationv1.MultiClusterSetting{ 686 Enabled: true, 687 }, 688 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 689 }, 690 } 691 692 _, err = executeAllocation(gsa, c) 693 assert.Error(t, err) 694 st, ok := status.FromError(err) 695 assert.True(t, ok) 696 assert.Equal(t, st.Code(), codes.DeadlineExceeded) 697 assert.Equal(t, 0, calls) 698 }) 699 t.Run("First allocation fails and second succeeds on the same server", func(t *testing.T) { 700 c, m := newFakeController() 701 fleetName := addReactorForGameServer(&m) 702 703 // Mock server to return DeadlineExceeded on the first call and success on subsequent ones 704 calls := 0 705 c.allocator.remoteAllocationCallback = func(_ context.Context, _ string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) { 706 calls++ 707 if calls == 1 { 708 return nil, status.Errorf(codes.DeadlineExceeded, "remote allocation call timeout") 709 } 710 return &pb.AllocationResponse{}, nil 711 } 712 713 // Allocation policy reactor 714 secretName := clusterName + "secret" 715 m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 716 return true, &multiclusterv1.GameServerAllocationPolicyList{ 717 Items: []multiclusterv1.GameServerAllocationPolicy{ 718 { 719 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 720 Priority: 1, 721 Weight: 200, 722 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 723 AllocationEndpoints: []string{unhealthyEndpoint}, 724 ClusterName: clusterName, 725 SecretName: secretName, 726 ServerCA: clientCert, 727 }, 728 }, 729 ObjectMeta: metav1.ObjectMeta{ 730 Namespace: defaultNs, 731 }, 732 }, 733 }, 734 }, nil 735 }) 736 737 m.KubeClient.AddReactor("list", "secrets", 738 func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 739 return true, getTestSecret(secretName, clientCert), nil 740 }) 741 742 ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced) 743 defer cancel() 744 745 // This call initializes the cache 746 err := c.allocator.allocationCache.syncCache() 747 assert.Nil(t, err) 748 749 err = c.allocator.allocationCache.counter.Run(ctx, 0) 750 assert.Nil(t, err) 751 752 gsa := &allocationv1.GameServerAllocation{ 753 ObjectMeta: metav1.ObjectMeta{ 754 Namespace: defaultNs, 755 Name: "alloc1", 756 }, 757 Spec: allocationv1.GameServerAllocationSpec{ 758 MultiClusterSetting: allocationv1.MultiClusterSetting{ 759 Enabled: true, 760 }, 761 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}}, 762 }, 763 } 764 765 _, err = executeAllocation(gsa, c) 766 assert.NoError(t, err) 767 assert.Equal(t, 2, calls) 768 }) 769 } 770 771 func executeAllocation(gsa *allocationv1.GameServerAllocation, c *Extensions) (*allocationv1.GameServerAllocation, error) { 772 r, err := createRequest(gsa) 773 if err != nil { 774 return nil, err 775 } 776 rec := httptest.NewRecorder() 777 if err := c.processAllocationRequest(context.Background(), rec, r, gsa.Namespace); err != nil { 778 return nil, err 779 } 780 781 ret := &allocationv1.GameServerAllocation{} 782 jsn := rec.Body.Bytes() 783 err = json.Unmarshal(jsn, ret) 784 return ret, errors.Wrapf(err, "failed to unmarshal allocation response: %s", jsn) 785 } 786 787 func addReactorForGameServer(m *agtesting.Mocks) string { 788 f, gsList := defaultFixtures(3) 789 gsWatch := watch.NewFake() 790 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 791 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) { 792 return true, &agonesv1.GameServerList{Items: gsList}, nil 793 }) 794 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) { 795 ua := action.(k8stesting.UpdateAction) 796 gs := ua.GetObject().(*agonesv1.GameServer) 797 gsWatch.Modify(gs) 798 return true, gs, nil 799 }) 800 return f.ObjectMeta.Name 801 } 802 803 func createRequest(gsa *allocationv1.GameServerAllocation) (*http.Request, error) { 804 buf := bytes.NewBuffer(nil) 805 if err := json.NewEncoder(buf).Encode(gsa); err != nil { 806 return nil, err 807 } 808 809 r, err := http.NewRequest(http.MethodPost, "/", buf) 810 if err != nil { 811 return nil, err 812 } 813 814 r.Header.Set("Content-Type", k8sruntime.ContentTypeJSON) 815 r.Header.Set("Accept", k8sruntime.ContentTypeJSON) 816 817 return r, nil 818 } 819 820 func defaultFixtures(gsLen int) (*agonesv1.Fleet, []agonesv1.GameServer) { 821 f := &agonesv1.Fleet{ 822 ObjectMeta: metav1.ObjectMeta{ 823 Name: "fleet-1", 824 Namespace: defaultNs, 825 UID: "1234", 826 }, 827 Spec: agonesv1.FleetSpec{ 828 Replicas: 5, 829 Template: agonesv1.GameServerTemplateSpec{}, 830 }, 831 } 832 f.ApplyDefaults() 833 gsSet := f.GameServerSet() 834 gsSet.ObjectMeta.Name = "gsSet1" 835 var gsList []agonesv1.GameServer 836 for i := 1; i <= gsLen; i++ { 837 gs := gsSet.GameServer() 838 gs.ObjectMeta.Name = "gs" + strconv.Itoa(i) 839 gs.Status.State = agonesv1.GameServerStateReady 840 gsList = append(gsList, *gs) 841 } 842 return f, gsList 843 } 844 845 // newFakeController returns a controller, backed by the fake Clientset 846 func newFakeController() (*Extensions, agtesting.Mocks) { 847 return newFakeControllerWithTimeout(10*time.Second, 30*time.Second) 848 } 849 850 // newFakeController returns a controller, backed by the fake Clientset with custom allocation timeouts 851 func newFakeControllerWithTimeout(remoteAllocationTimeout time.Duration, totalRemoteAllocationTimeout time.Duration) (*Extensions, agtesting.Mocks) { 852 m := agtesting.NewMocks() 853 m.Mux = http.NewServeMux() 854 counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory) 855 api := apiserver.NewAPIServer(m.Mux) 856 c := NewExtensions(api, healthcheck.NewHandler(), counter, m.KubeClient, m.KubeInformerFactory, m.AgonesClient, m.AgonesInformerFactory, remoteAllocationTimeout, totalRemoteAllocationTimeout, 500*time.Millisecond) 857 c.recorder = m.FakeRecorder 858 c.allocator.recorder = m.FakeRecorder 859 return c, m 860 } 861 862 func getTestSecret(secretName string, serverCert []byte) *corev1.SecretList { 863 return &corev1.SecretList{ 864 Items: []corev1.Secret{ 865 { 866 Data: map[string][]byte{ 867 "ca.crt": serverCert, 868 "tls.key": clientKey, 869 "tls.crt": clientCert, 870 }, 871 ObjectMeta: metav1.ObjectMeta{ 872 Name: secretName, 873 Namespace: defaultNs, 874 }, 875 }, 876 }, 877 } 878 } 879 880 var clientCert = []byte(`-----BEGIN CERTIFICATE----- 881 MIIDuzCCAqOgAwIBAgIUduDWtqpUsp3rZhCEfUrzI05laVIwDQYJKoZIhvcNAQEL 882 BQAwbTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u 883 ZG9uMRgwFgYDVQQKDA9HbG9iYWwgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFy 884 dG1lbnQxCjAIBgNVBAMMASowHhcNMTkwNTAyMjIzMDQ3WhcNMjkwNDI5MjIzMDQ3 885 WjBtMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25k 886 b24xGDAWBgNVBAoMD0dsb2JhbCBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0 887 bWVudDEKMAgGA1UEAwwBKjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 888 AKGDasjadVwe0bXUEQfZCkMEAkzn0qTud3RYytympmaS0c01SWFNZwPRO0rpdIOZ 889 fyXVXVOAhgmgCR6QuXySmyQIoYl/D6tVhc5r9FyWPIBtzQKCJTX0mZOZwMn22qvo 890 bfnDnVsZ1Ny3RLZIF3um3xovvePXyg1z7D/NvCogNuYpyUUEITPZX6ss5ods/U78 891 BxLhKrT8iyu61ZC+ZegbHQqFRngbeb348gE1JwKTslDfe4oH7tZ+bNDZxnGcvh9j 892 eyagpM0zys4gFfQf/vfD2aEsUJ+GesUQC6uGVoGnTFshFhBsAK6vpIQ4ZQujaJ0r 893 NKgJ/ccBJFiJXMCR44yWFY0CAwEAAaNTMFEwHQYDVR0OBBYEFEe1gDd8JpzgnvOo 894 1AEloAXxmxHCMB8GA1UdIwQYMBaAFEe1gDd8JpzgnvOo1AEloAXxmxHCMA8GA1Ud 895 EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAI5GyuakVgunerCGCSN7Ghsr 896 ys9vJytbyT+BLmxNBPSXWQwcm3g9yCDdgf0Y3q3Eef7IEZu4I428318iLzhfKln1 897 ua4fxvmTFKJ65lQKNkc6Y4e3w1t+C2HOl6fOIVT231qsCoM5SAwQQpqAzEUj6kZl 898 x+3avw9KSlXqR/mCAkePyoKvprxeb6RVDdq92Ug0qzoAHLpvIkuHdlF0dNp6/kO0 899 1pVL0BqW+6UTimSSvH8F/cMeYKbkhpE1u2c/NtNwsR2jN4M9kl3KHqkynk67PfZv 900 pwlCqZx4M8FpdfCbOZeRLzClUBdD5qzev0L3RNUx7UJzEIN+4LCBv37DIojNOyA= 901 -----END CERTIFICATE-----`) 902 903 var clientKey = []byte(`-----BEGIN PRIVATE KEY----- 904 MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChg2rI2nVcHtG1 905 1BEH2QpDBAJM59Kk7nd0WMrcpqZmktHNNUlhTWcD0TtK6XSDmX8l1V1TgIYJoAke 906 kLl8kpskCKGJfw+rVYXOa/RcljyAbc0CgiU19JmTmcDJ9tqr6G35w51bGdTct0S2 907 SBd7pt8aL73j18oNc+w/zbwqIDbmKclFBCEz2V+rLOaHbP1O/AcS4Sq0/IsrutWQ 908 vmXoGx0KhUZ4G3m9+PIBNScCk7JQ33uKB+7WfmzQ2cZxnL4fY3smoKTNM8rOIBX0 909 H/73w9mhLFCfhnrFEAurhlaBp0xbIRYQbACur6SEOGULo2idKzSoCf3HASRYiVzA 910 keOMlhWNAgMBAAECggEAaRPDjEq8IaOXUdFXByEIERNxn7EOlOjj5FjEGgt9pKwO 911 PJBXXitqQsyD47fAasGZO/b1EZdDHM32QOFtG4OR1T6cQYTdn90zAVmwj+/aCr/k 912 qaYcKV8p7yIPkBW+rCq6Kc0++X7zwmilFmYOiQ7GhRXcV3gTZu8tG1FxAoMU1GYA 913 WoGiu+UsEm0MFIOwV/DOukDaj6j4Q9wD0tqi2MsjrugjDI8/mSx5mlvo3yZHubl0 914 ChQaWZyUlL2B40mQJc3qsRZzso3sbU762L6G6npQJ19dHgsBfBBs/Q4/DdeqcOb4 915 Q9OZ8Q3Q5nXQ7359Sh94LvLOoaWecRTBPGaRvGAGLQKBgQDTOZPEaJJI9heUQ0Ar 916 VvUuyjILv8CG+PV+rGZ7+yMFCUlmM/m9I0IIc3WbmxxiRypBv46zxpczQHwWZRf2 917 7IUZdyrBXRtNoaXbWh3dSgqa7WuHGUzqmn+98sQDodewCyGon8LG9atyge8vFo/l 918 N0Y21duYj4NeJod82Y0RAKsuzwKBgQDDwCuvbq0FkugklUr5WLFrYTzWrTYPio5k 919 ID6Ku57yaZNVRv52FTF3Ac5LoKGCv8iPg+x0SiTmCbW2DF2ohvTuJy1H/unJ4bYG 920 B9vEVOiScbvrvuQ6iMgfxNUCEEQvmn6+uc+KHVwPixY4j6/q1ZLXLPbjqXYHPYi+ 921 lx9ZG0As4wKBgDj52QAr7Pm9WBLoKREHvc9HP0SoDrjZwu7Odj6POZ0MKj5lWsJI 922 FnHNIzY8GuXvqFhf4ZBgyzxJ8q7fyh0TI7wAxwmtocXJCsImhtPAOygbTtv8WSEX 923 V8nXCESqjVGxTvz7S0D716llny0met4rkMcN3NREMf1di0KENGcXtRVFAoGBAKs3 924 bD5/NNF6RJizCKf+fvjoTVmMmYuQaqmDVpDsOMPZumfNuAa61NA+AR4/OuXtL9Tv 925 1COHMq0O8yRvvoAIwzWHiOC/Q+g0B41Q1FXu2po05uT1zBSyzTCUbqfmaG2m2ZOj 926 XLd2pK5nvqDsdTeXZV/WUYCiGb2Ngg0Ki/3ZixF3AoGACwPxxoAWkuD6T++35Vdt 927 OxAh/qyGMtgfvdBJPfA3u4digTckBDTwYBhrmvC2Vuc4cpb15RYuUT/M+c3gS3P0 928 q+2uLIuwciETPD7psK76NsQM3ZL/IEaZB3VMxbMMFn/NQRbmntTd/twZ42zieX+R 929 2VpXYUjoRcuir2oU0wh3Hic= 930 -----END PRIVATE KEY-----`)