agones.dev/agones@v1.54.0/pkg/gameservers/health_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 gameservers 16 17 import ( 18 "context" 19 "errors" 20 "testing" 21 "time" 22 23 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 24 agtesting "agones.dev/agones/pkg/testing" 25 agruntime "agones.dev/agones/pkg/util/runtime" 26 "github.com/heptiolabs/healthcheck" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/apimachinery/pkg/watch" 34 k8stesting "k8s.io/client-go/testing" 35 ) 36 37 func TestHealthControllerFailedContainer(t *testing.T) { 38 t.Parallel() 39 40 agruntime.FeatureTestMutex.Lock() 41 defer agruntime.FeatureTestMutex.Unlock() 42 require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=false")) 43 44 m := agtesting.NewMocks() 45 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 46 47 gs := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: newSingleContainerSpec()} 48 gs.ApplyDefaults() 49 50 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 51 require.NoError(t, err) 52 pod.Status = corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{Name: gs.Spec.Container, 53 State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}}}} 54 55 assert.True(t, hc.failedContainer(pod)) 56 pod2 := pod.DeepCopy() 57 58 pod.Status.ContainerStatuses[0].State.Terminated = nil 59 assert.False(t, hc.failedContainer(pod)) 60 61 pod.Status.ContainerStatuses[0].LastTerminationState.Terminated = &corev1.ContainerStateTerminated{} 62 assert.True(t, hc.failedContainer(pod)) 63 64 pod2.Status.ContainerStatuses[0].Name = "Not a matching name" 65 assert.False(t, hc.failedContainer(pod2)) 66 } 67 68 func TestHealthControllerFailedPod(t *testing.T) { 69 t.Parallel() 70 71 agruntime.FeatureTestMutex.Lock() 72 defer agruntime.FeatureTestMutex.Unlock() 73 74 // set sidecar feature flag 75 require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=true")) 76 77 m := agtesting.NewMocks() 78 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 79 80 gs := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: newSingleContainerSpec()} 81 gs.ApplyDefaults() 82 83 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 84 require.NoError(t, err) 85 assert.False(t, hc.failedPod(pod)) 86 87 // set the pod to failed status 88 pod.Status.Phase = corev1.PodFailed 89 assert.True(t, hc.failedPod(pod)) 90 } 91 92 func TestHealthUnschedulableWithNoFreePorts(t *testing.T) { 93 t.Parallel() 94 95 agruntime.FeatureTestMutex.Lock() 96 defer agruntime.FeatureTestMutex.Unlock() 97 require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=false")) 98 99 m := agtesting.NewMocks() 100 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 101 102 gs := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: newSingleContainerSpec()} 103 gs.ApplyDefaults() 104 105 for name, tc := range map[string]struct { 106 message string 107 waitOnFreePorts bool 108 wantUnschedulable bool 109 }{ 110 "unschedulable, terminal": { 111 message: "0/4 nodes are available: 4 node(s) didn't have free ports for the requestedpod ports.", 112 wantUnschedulable: true, 113 }, 114 "unschedulable, will wait on free ports": { 115 message: "0/4 nodes are available: 4 node(s) didn't have free ports for the requestedpod ports.", 116 waitOnFreePorts: true, 117 }, 118 "some other condition": { 119 message: "twas brillig and the slithy toves", 120 }, 121 } { 122 t.Run(name, func(t *testing.T) { 123 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 124 require.NoError(t, err) 125 126 pod.Status.Conditions = []corev1.PodCondition{ 127 {Type: corev1.PodScheduled, Reason: corev1.PodReasonUnschedulable, 128 Message: tc.message}, 129 } 130 hc.waitOnFreePorts = tc.waitOnFreePorts 131 assert.Equal(t, tc.wantUnschedulable, hc.unschedulableWithNoFreePorts(pod)) 132 }) 133 } 134 } 135 136 func TestHealthControllerSkipUnhealthyGameContainer(t *testing.T) { 137 t.Parallel() 138 139 agruntime.FeatureTestMutex.Lock() 140 defer agruntime.FeatureTestMutex.Unlock() 141 require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=false")) 142 143 type expected struct { 144 result bool 145 err string 146 } 147 148 fixtures := map[string]struct { 149 setup func(*agonesv1.GameServer, *corev1.Pod) 150 expected expected 151 }{ 152 "scheduled and terminated container": { 153 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 154 gs.Status.State = agonesv1.GameServerStateScheduled 155 pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ 156 Name: gs.Spec.Container, 157 State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, 158 }} 159 }, 160 expected: expected{result: true}, 161 }, 162 "after ready and terminated container": { 163 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 164 gs.Status.State = agonesv1.GameServerStateReady 165 pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ 166 Name: gs.Spec.Container, 167 State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, 168 }} 169 }, 170 expected: expected{result: false}, 171 }, 172 "before ready, with no terminated container": { 173 setup: func(gs *agonesv1.GameServer, _ *corev1.Pod) { 174 gs.Status.State = agonesv1.GameServerStateScheduled 175 }, 176 expected: expected{result: false}, 177 }, 178 "after ready, with no terminated container": { 179 setup: func(gs *agonesv1.GameServer, _ *corev1.Pod) { 180 gs.Status.State = agonesv1.GameServerStateAllocated 181 }, 182 expected: expected{result: false}, 183 }, 184 "before ready, with a LastTerminated container": { 185 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 186 gs.Status.State = agonesv1.GameServerStateScheduled 187 pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ 188 Name: gs.Spec.Container, 189 LastTerminationState: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, 190 }} 191 }, 192 expected: expected{result: true}, 193 }, 194 "after ready, with a LastTerminated container, not matching": { 195 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 196 gs.Status.State = agonesv1.GameServerStateReady 197 gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "4321" 198 pod.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "4321" 199 pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ 200 ContainerID: "1234", 201 Name: gs.Spec.Container, 202 LastTerminationState: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, 203 }} 204 }, 205 expected: expected{result: false}, 206 }, 207 "after ready, with a LastTerminated container, matching": { 208 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 209 gs.Status.State = agonesv1.GameServerStateReserved 210 gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "1234" 211 pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "1234" 212 pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ 213 ContainerID: "1234", 214 Name: gs.Spec.Container, 215 LastTerminationState: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, 216 }} 217 }, 218 expected: expected{result: true}, 219 }, 220 "pod is missing!": { 221 setup: func(_ *agonesv1.GameServer, pod *corev1.Pod) { 222 pod.ObjectMeta.Name = "missing" 223 }, 224 expected: expected{result: false}, 225 }, 226 "annotations do not match": { 227 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 228 gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "1234" 229 pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "" 230 }, 231 expected: expected{err: "pod and gameserver test data are out of sync, retrying"}, 232 }, 233 } 234 235 for k, v := range fixtures { 236 t.Run(k, func(t *testing.T) { 237 m := agtesting.NewMocks() 238 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 239 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: defaultNs}, Spec: newSingleContainerSpec()} 240 gs.ApplyDefaults() 241 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 242 assert.NoError(t, err) 243 244 v.setup(gs, pod) 245 246 m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) { 247 return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil 248 }) 249 250 result, err := hc.skipUnhealthyGameContainer(gs, pod) 251 252 if len(v.expected.err) > 0 { 253 require.EqualError(t, err, v.expected.err) 254 } else { 255 assert.NoError(t, err) 256 } 257 assert.Equal(t, v.expected.result, result) 258 }) 259 } 260 } 261 262 func TestHealthControllerSyncGameServer(t *testing.T) { 263 t.Parallel() 264 265 type expected struct { 266 updated bool 267 } 268 fixtures := map[string]struct { 269 state agonesv1.GameServerState 270 podStatus *corev1.PodStatus 271 feature string 272 expected expected 273 }{ 274 "started": { 275 state: agonesv1.GameServerStateStarting, 276 expected: expected{ 277 updated: true, 278 }, 279 }, 280 "shutdown": { 281 state: agonesv1.GameServerStateShutdown, 282 expected: expected{ 283 updated: false, 284 }, 285 }, 286 "unhealthy": { 287 state: agonesv1.GameServerStateUnhealthy, 288 expected: expected{ 289 updated: false, 290 }, 291 }, 292 "ready": { 293 state: agonesv1.GameServerStateReady, 294 expected: expected{ 295 updated: true, 296 }, 297 }, 298 "allocated": { 299 state: agonesv1.GameServerStateAllocated, 300 expected: expected{ 301 updated: true, 302 }, 303 }, 304 "container failed before ready": { 305 state: agonesv1.GameServerStateStarting, 306 podStatus: &corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{ 307 {Name: "container", State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}}}}, 308 expected: expected{updated: false}, 309 }, 310 "container failed after ready": { 311 state: agonesv1.GameServerStateAllocated, 312 feature: string(agruntime.FeatureSidecarContainers) + "=false", 313 podStatus: &corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{ 314 {Name: "container", State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}}}}, 315 expected: expected{updated: true}, 316 }, 317 "container recovered and starting after queueing": { 318 state: agonesv1.GameServerStateStarting, 319 podStatus: &corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{ 320 {Name: "container", State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{}}}}}, 321 expected: expected{updated: false}, 322 }, 323 "container recovered and ready after queueing": { 324 state: agonesv1.GameServerStateReady, 325 podStatus: &corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{ 326 {Name: "container", State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}}}}, 327 expected: expected{updated: false}, 328 }, 329 "container recovered and allocated after queueing": { 330 state: agonesv1.GameServerStateAllocated, 331 podStatus: &corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{ 332 {Name: "container", State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}}}}, 333 expected: expected{updated: false}, 334 }, 335 "pod failed": { 336 feature: string(agruntime.FeatureSidecarContainers) + "=true", 337 state: agonesv1.GameServerStateReady, 338 podStatus: &corev1.PodStatus{Phase: corev1.PodFailed}, 339 expected: expected{updated: true}, 340 }, 341 } 342 343 for name, test := range fixtures { 344 t.Run(name, func(t *testing.T) { 345 agruntime.FeatureTestMutex.Lock() 346 defer agruntime.FeatureTestMutex.Unlock() 347 if len(test.feature) > 0 { 348 require.NoError(t, agruntime.ParseFeatures(test.feature)) 349 } 350 351 m := agtesting.NewMocks() 352 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 353 hc.recorder = m.FakeRecorder 354 355 gs := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test"}, Spec: newSingleContainerSpec(), 356 Status: agonesv1.GameServerStatus{State: test.state}} 357 gs.ApplyDefaults() 358 359 got := false 360 updated := false 361 m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) { 362 list := &corev1.PodList{Items: []corev1.Pod{}} 363 if test.podStatus != nil { 364 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 365 assert.NoError(t, err) 366 pod.Status = *test.podStatus 367 list.Items = append(list.Items, *pod) 368 } 369 return true, list, nil 370 }) 371 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 372 got = true 373 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{gs}}, nil 374 }) 375 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 376 updated = true 377 ua := action.(k8stesting.UpdateAction) 378 gsObj := ua.GetObject().(*agonesv1.GameServer) 379 assert.Equal(t, agonesv1.GameServerStateUnhealthy, gsObj.Status.State) 380 return true, gsObj, nil 381 }) 382 383 ctx, cancel := agtesting.StartInformers(m, hc.gameServerSynced, hc.podSynced) 384 defer cancel() 385 386 err := hc.syncGameServer(ctx, "default/test") 387 assert.Nil(t, err, err) 388 assert.True(t, got, "GameServers Should be got!") 389 390 assert.Equal(t, test.expected.updated, updated, "updated test") 391 }) 392 } 393 } 394 395 func TestHealthControllerSyncGameServerUpdateFailed(t *testing.T) { 396 t.Parallel() 397 398 m := agtesting.NewMocks() 399 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 400 hc.recorder = m.FakeRecorder 401 402 gs := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test"}, Spec: newSingleContainerSpec(), 403 Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateAllocated}} 404 gs.ApplyDefaults() 405 406 m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) { 407 list := &corev1.PodList{Items: []corev1.Pod{}} 408 return true, list, nil 409 }) 410 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 411 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{gs}}, nil 412 }) 413 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 414 ua := action.(k8stesting.UpdateAction) 415 gsObj := ua.GetObject().(*agonesv1.GameServer) 416 assert.Equal(t, agonesv1.GameServerStateUnhealthy, gsObj.Status.State) 417 return true, gsObj, errors.New("update-err") 418 }) 419 420 ctx, cancel := agtesting.StartInformers(m, hc.gameServerSynced, hc.podSynced) 421 defer cancel() 422 423 err := hc.syncGameServer(ctx, "default/test") 424 425 if assert.Error(t, err) { 426 assert.Equal(t, "error updating GameServer test/default to unhealthy: update-err", err.Error()) 427 } 428 } 429 430 func TestHealthControllerRunNoSideCar(t *testing.T) { 431 t.Parallel() 432 433 agruntime.FeatureTestMutex.Lock() 434 defer agruntime.FeatureTestMutex.Unlock() 435 require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=false")) 436 437 m := agtesting.NewMocks() 438 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 439 hc.recorder = m.FakeRecorder 440 441 gsWatch := watch.NewFake() 442 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 443 444 podWatch := watch.NewFake() 445 m.KubeClient.AddWatchReactor("pods", k8stesting.DefaultWatchReactor(podWatch, nil)) 446 447 updated := make(chan bool) 448 defer close(updated) 449 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 450 defer func() { 451 updated <- true 452 }() 453 ua := action.(k8stesting.UpdateAction) 454 gsObj := ua.GetObject().(*agonesv1.GameServer) 455 assert.Equal(t, agonesv1.GameServerStateUnhealthy, gsObj.Status.State) 456 return true, gsObj, nil 457 }) 458 459 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test"}, Spec: newSingleContainerSpec(), 460 Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}} 461 gs.ApplyDefaults() 462 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 463 require.NoError(t, err) 464 465 stop, cancel := agtesting.StartInformers(m) 466 defer cancel() 467 468 gsWatch.Add(gs.DeepCopy()) 469 podWatch.Add(pod.DeepCopy()) 470 471 go hc.Run(stop, 1) // nolint: errcheck 472 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 473 return hc.workerqueue.RunCount() == 1, nil 474 }) 475 assert.NoError(t, err) 476 477 pod.Status.ContainerStatuses = []corev1.ContainerStatus{{Name: gs.Spec.Container, State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}}} 478 // gate 479 assert.True(t, hc.failedContainer(pod)) 480 assert.False(t, hc.unschedulableWithNoFreePorts(pod)) 481 482 podWatch.Modify(pod.DeepCopy()) 483 484 select { 485 case <-updated: 486 case <-time.After(10 * time.Second): 487 assert.FailNow(t, "timeout on GameServer update") 488 } 489 490 agtesting.AssertEventContains(t, m.FakeRecorder.Events, string(agonesv1.GameServerStateUnhealthy)) 491 492 pod.Status.ContainerStatuses = nil 493 pod.Status.Conditions = []corev1.PodCondition{ 494 {Type: corev1.PodScheduled, Reason: corev1.PodReasonUnschedulable, 495 Message: "0/4 nodes are available: 4 node(s) didn't have free ports for the requestedpod ports."}, 496 } 497 // gate 498 assert.True(t, hc.unschedulableWithNoFreePorts(pod)) 499 assert.False(t, hc.failedContainer(pod)) 500 501 podWatch.Modify(pod.DeepCopy()) 502 503 select { 504 case <-updated: 505 case <-time.After(10 * time.Second): 506 assert.FailNow(t, "timeout on GameServer update") 507 } 508 509 agtesting.AssertEventContains(t, m.FakeRecorder.Events, string(agonesv1.GameServerStateUnhealthy)) 510 511 podWatch.Delete(pod.DeepCopy()) 512 select { 513 case <-updated: 514 case <-time.After(10 * time.Second): 515 assert.FailNow(t, "timeout on GameServer update") 516 } 517 518 agtesting.AssertEventContains(t, m.FakeRecorder.Events, string(agonesv1.GameServerStateUnhealthy)) 519 } 520 521 func TestHealthControllerRunWithSideCar(t *testing.T) { 522 t.Parallel() 523 524 agruntime.FeatureTestMutex.Lock() 525 defer agruntime.FeatureTestMutex.Unlock() 526 require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=true")) 527 528 m := agtesting.NewMocks() 529 hc := NewHealthController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory, false) 530 hc.recorder = m.FakeRecorder 531 532 gsWatch := watch.NewFake() 533 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 534 535 podWatch := watch.NewFake() 536 m.KubeClient.AddWatchReactor("pods", k8stesting.DefaultWatchReactor(podWatch, nil)) 537 538 updated := make(chan bool) 539 defer close(updated) 540 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 541 defer func() { 542 updated <- true 543 }() 544 ua := action.(k8stesting.UpdateAction) 545 gsObj := ua.GetObject().(*agonesv1.GameServer) 546 assert.Equal(t, agonesv1.GameServerStateUnhealthy, gsObj.Status.State) 547 return true, gsObj, nil 548 }) 549 550 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test"}, Spec: newSingleContainerSpec(), 551 Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}} 552 gs.ApplyDefaults() 553 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 554 require.NoError(t, err) 555 556 stop, cancel := agtesting.StartInformers(m) 557 defer cancel() 558 559 gsWatch.Add(gs.DeepCopy()) 560 podWatch.Add(pod.DeepCopy()) 561 562 go hc.Run(stop, 1) // nolint: errcheck 563 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 564 return hc.workerqueue.RunCount() == 1, nil 565 }) 566 assert.NoError(t, err) 567 568 pod.Status = corev1.PodStatus{Phase: corev1.PodFailed} 569 // gate 570 require.True(t, hc.failedPod(pod)) 571 require.False(t, hc.evictedPod(pod)) 572 573 podWatch.Modify(pod.DeepCopy()) 574 575 select { 576 case <-updated: 577 case <-time.After(10 * time.Second): 578 assert.FailNow(t, "timeout on GameServer update") 579 } 580 581 agtesting.AssertEventContains(t, m.FakeRecorder.Events, string(agonesv1.GameServerStateUnhealthy)) 582 583 podWatch.Delete(pod.DeepCopy()) 584 select { 585 case <-updated: 586 case <-time.After(10 * time.Second): 587 assert.FailNow(t, "timeout on GameServer update") 588 } 589 590 agtesting.AssertEventContains(t, m.FakeRecorder.Events, string(agonesv1.GameServerStateUnhealthy)) 591 }