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  }