agones.dev/agones@v1.54.0/pkg/gameservers/missing_test.go (about) 1 // Copyright 2020 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 "sync/atomic" 20 "testing" 21 "time" 22 23 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 24 agtesting "agones.dev/agones/pkg/testing" 25 "github.com/heptiolabs/healthcheck" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/watch" 32 k8stesting "k8s.io/client-go/testing" 33 "k8s.io/client-go/tools/cache" 34 ) 35 36 func TestMissingPodControllerSyncGameServer(t *testing.T) { 37 type expected struct { 38 updated bool 39 updateTests func(t *testing.T, gs *agonesv1.GameServer) 40 postTests func(t *testing.T, mocks agtesting.Mocks) 41 } 42 fixtures := map[string]struct { 43 setup func(*agonesv1.GameServer, *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) 44 expected expected 45 }{ 46 "pod exists": { 47 setup: func(gs *agonesv1.GameServer, pod *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) { 48 return gs, pod 49 }, 50 expected: expected{ 51 updated: false, 52 updateTests: func(_ *testing.T, _ *agonesv1.GameServer) {}, 53 postTests: func(_ *testing.T, _ agtesting.Mocks) {}, 54 }, 55 }, 56 "pod doesn't exist: game server is fine": { 57 setup: func(gs *agonesv1.GameServer, _ *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) { 58 return gs, nil 59 }, 60 expected: expected{ 61 updated: true, 62 updateTests: func(t *testing.T, gs *agonesv1.GameServer) { 63 assert.Equal(t, agonesv1.GameServerStateUnhealthy, gs.Status.State) 64 }, 65 postTests: func(t *testing.T, m agtesting.Mocks) { 66 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Warning Unhealthy Pod is missing") 67 }, 68 }, 69 }, 70 "pod doesn't exist: game server not found": { 71 setup: func(_ *agonesv1.GameServer, _ *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) { 72 return nil, nil 73 }, 74 expected: expected{ 75 updated: false, 76 updateTests: func(_ *testing.T, _ *agonesv1.GameServer) {}, 77 postTests: func(_ *testing.T, _ agtesting.Mocks) {}, 78 }, 79 }, 80 "pod doesn't exist: game server is being deleted": { 81 setup: func(gs *agonesv1.GameServer, _ *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) { 82 now := metav1.Now() 83 gs.ObjectMeta.DeletionTimestamp = &now 84 return gs, nil 85 }, 86 expected: expected{ 87 updated: false, 88 updateTests: func(_ *testing.T, _ *agonesv1.GameServer) {}, 89 postTests: func(_ *testing.T, _ agtesting.Mocks) {}, 90 }, 91 }, 92 "pod doesn't exist: game server is already Unhealthy": { 93 setup: func(gs *agonesv1.GameServer, _ *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) { 94 gs.Status.State = agonesv1.GameServerStateUnhealthy 95 return gs, nil 96 }, 97 expected: expected{ 98 updated: false, 99 updateTests: func(_ *testing.T, _ *agonesv1.GameServer) {}, 100 postTests: func(_ *testing.T, _ agtesting.Mocks) {}, 101 }, 102 }, 103 "pod is not a gameserver pod": { 104 setup: func(gs *agonesv1.GameServer, _ *corev1.Pod) (*agonesv1.GameServer, *corev1.Pod) { 105 return gs, &corev1.Pod{ObjectMeta: gs.ObjectMeta} 106 }, 107 expected: expected{ 108 updated: true, 109 updateTests: func(t *testing.T, gs *agonesv1.GameServer) { 110 assert.Equal(t, agonesv1.GameServerStateUnhealthy, gs.Status.State) 111 }, 112 postTests: func(t *testing.T, m agtesting.Mocks) { 113 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Warning Unhealthy Pod is missing") 114 }, 115 }, 116 }, 117 } 118 119 for k, v := range fixtures { 120 t.Run(k, func(t *testing.T) { 121 m := agtesting.NewMocks() 122 c := NewMissingPodController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory) 123 c.recorder = m.FakeRecorder 124 125 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, 126 Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{}} 127 gs.ApplyDefaults() 128 129 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 130 assert.NoError(t, err) 131 132 gs, pod = v.setup(gs, pod) 133 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 134 if gs != nil { 135 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs}}, nil 136 } 137 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{}}, nil 138 }) 139 m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) { 140 if pod != nil { 141 return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil 142 } 143 return true, &corev1.PodList{Items: []corev1.Pod{}}, nil 144 }) 145 146 updated := false 147 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 148 updated = true 149 ua := action.(k8stesting.UpdateAction) 150 gs := ua.GetObject().(*agonesv1.GameServer) 151 v.expected.updateTests(t, gs) 152 return true, gs, nil 153 }) 154 ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced) 155 defer cancel() 156 157 err = c.syncGameServer(ctx, "default/test") 158 assert.NoError(t, err) 159 assert.Equal(t, v.expected.updated, updated, "updated state") 160 v.expected.postTests(t, m) 161 }) 162 } 163 } 164 165 func TestMissingPodControllerRun(t *testing.T) { 166 m := agtesting.NewMocks() 167 c := NewMissingPodController(healthcheck.NewHandler(), m.KubeClient, m.AgonesClient, m.KubeInformerFactory, m.AgonesInformerFactory) 168 169 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, 170 Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{}} 171 gs.ApplyDefaults() 172 173 received := make(chan string) 174 h := func(_ context.Context, name string) error { 175 assert.Equal(t, "default/test", name) 176 received <- name 177 return nil 178 } 179 180 gsWatch := watch.NewFake() 181 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 182 183 // do we have a pod? 184 exist := atomic.Bool{} 185 exist.Store(false) 186 187 m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) { 188 if exist.Load() { 189 pod, err := gs.Pod(agtesting.FakeAPIHooks{}) 190 require.NoError(t, err) 191 return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil 192 } 193 return true, &corev1.PodList{Items: []corev1.Pod{}}, nil 194 }) 195 196 c.workerqueue.SyncHandler = h 197 198 ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced) 199 defer cancel() 200 201 go func() { 202 err := c.Run(ctx, 1) 203 assert.Nil(t, err, "Run should not error") 204 }() 205 206 noChange := func() { 207 assert.True(t, cache.WaitForCacheSync(ctx.Done(), c.gameServerSynced, c.podSynced)) 208 select { 209 case <-received: 210 assert.FailNow(t, "should not run sync") 211 default: 212 } 213 } 214 215 result := func() { 216 select { 217 case res := <-received: 218 assert.Equal(t, "default/test", res) 219 case <-time.After(2 * time.Second): 220 assert.FailNow(t, "did not run sync") 221 } 222 } 223 224 // initial population 225 gsWatch.Add(gs.DeepCopy()) 226 noChange() 227 228 // gs before pod 229 gs.Status.State = agonesv1.GameServerStatePortAllocation 230 gsWatch.Modify(gs.DeepCopy()) 231 noChange() 232 233 // ready gs 234 exist.Store(true) 235 gs.Status.State = agonesv1.GameServerStateReady 236 gsWatch.Modify(gs.DeepCopy()) 237 result() 238 239 // allocated 240 gs.Status.State = agonesv1.GameServerStateAllocated 241 gsWatch.Modify(gs.DeepCopy()) 242 result() 243 244 // unhealthy gs 245 gs.Status.State = agonesv1.GameServerStateUnhealthy 246 gsWatch.Modify(gs.DeepCopy()) 247 noChange() 248 249 // shutdown gs 250 gs.Status.State = agonesv1.GameServerStateShutdown 251 gsWatch.Modify(gs.DeepCopy()) 252 noChange() 253 254 // dev gameservers 255 gs.Status.State = agonesv1.GameServerStateReady 256 gs.ObjectMeta.Annotations[agonesv1.DevAddressAnnotation] = ipFixture 257 gsWatch.Modify(gs.DeepCopy()) 258 noChange() 259 }